Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)


前言

本项目结合了 S7.NetTIA Portal V16S7-PLCSIM Advanced V3.0,用于模拟 西门子 PLC 与Unity进行通信。实现制作工业仿真项目的数据通信功能:

1.CSV配置文件读取功能
2.Plc数据类型与C#数据类型互相转换
3.监听多个Plc数值变化
4.Plc数据的读取和写入
5.多个PLC创建和管理
6.支持PC/Web版本通讯


一、Unity Plc 工业仿真项目是什么?

Unity PLC 工业仿真项目 是指使用 Unity 引擎 结合 PLC(可编程逻辑控制器) 进行 工业自动化仿真 的项目。其核心目标是:
✅ 在PC端模拟工业设备的运行
✅ 使用PLC控制Unity内的3D场景和设备
✅ 实现虚拟工厂、HMI(人机界面)、流程仿真等功能

可以看看我上一篇文章的介绍: Unity开发记录_6_PC工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 和 Robot Studio 进行工业仿真

为什么要做PLC工业仿真?

在现实工业场景中,PLC广泛用于自动化控制,如工厂生产线、机器人控制、物流输送等。
但直接在真实设备上调试PLC程序,成本高、风险大,因此使用 Unity + PLC 进行仿真有如下优势:
✅ 降低设备损坏风险:不用在真实机器上调试,避免误操作导致损坏。
✅ 提高开发效率:可以在PC端快速测试PLC逻辑,无需等待硬件安装调试。
✅ 远程监控和培训:适用于工业培训和远程运维,可模拟不同的生产场景。

Unity 如何与 PLC 进行通信?

在 Unity + PLC 仿真项目中,Unity 需要与PLC进行 数据交互,常见的通信方式有:
S7.Net(C# 库,适用于西门子 S7 系列)
Modbus TCP(通用协议,适用于多种PLC品牌)
OPC UA(适用于工业物联网,数据交互标准)

二、效果展示

1.PC端通信效果

在这里插入图片描述


2.Web端通信效果

在这里插入图片描述


三、项目地址

GitHub 链接: UnityPlcProject_PC_Web
在这里插入图片描述

四、主要代码介绍

1.PlcManager

封装所有Plc调用功能

using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using static ATF.MDataItem;
using Debug = UnityEngine.Debug;

namespace ATF
{
    /// <summary>
    /// 3个不同的plc程序--示例 这个地方可以后期更改
    /// </summary>
    public enum EPlcName
    {
        EPN_FenJian,
        EPN_JiXieShou,
        EPN_CangKu,
    }

    /// <summary>
    /// web端通讯的消息类型
    /// </summary>
    public enum DESType
    {
        DEST_PlcConnect,                    //Plc 连接
        DEST_DisPlcConnect,                 //Plc 取消连接
        DEST_AddListenerPLCValueChange,     //监听Plc数据变化
        DEST_RemoveListenerPLCValueChange,  //移除Plc数据监听
        DEST_WriteValue,                    //写入数值
        DEST_IsConnected,                   //是否连接
        DEST_ReadValue,                     //读取数值
    }

    /// <summary>
    /// Plc管理单例,所有Plc相关的操作 都从这调用
    /// </summary>
    public class PlcManager : UnitySingletonTemplate<PlcManager>
    {
        private int m_plcUpdateSpeed = 100;//Plc数据更新的速率: 100ms
        private Dictionary<EPlcName, PlcVariable> m_plcDic = new Dictionary<EPlcName, PlcVariable>();
        private Process m_webPlcProcess;

        //[DllImport("__Internal")]
        //private static extern void OpenExe(string path,string args);

        private async void Start()
        {
            m_plcDic.Add(EPlcName.EPN_FenJian, new PlcVariable(await ReadPLCData(PlcConstant.PlcConfigDataPath_FenJian), m_plcUpdateSpeed));
            m_plcDic.Add(EPlcName.EPN_JiXieShou, new PlcVariable(await ReadPLCData(PlcConstant.PlcConfigDataPath_JiXieShou), m_plcUpdateSpeed));
            m_plcDic.Add(EPlcName.EPN_CangKu, new PlcVariable(await ReadPLCData(PlcConstant.PlcConfigDataPath_CangKu), m_plcUpdateSpeed));

#if UNITY_WEBGL

#if UNITY_EDITOR
            m_webPlcProcess = new Process();
            m_webPlcProcess.StartInfo = new ProcessStartInfo(Application.streamingAssetsPath + PlcConstant.WebPlcServerPath, Application.streamingAssetsPath);
            m_webPlcProcess.Start();
#else
            //Debug.Log("开始调用js代码启动外部服务器...");
            //string streamingAssetsPath = Application.streamingAssetsPath.TrimEnd('/');
            //Debug.Log("streamingAssetsPath:  " + streamingAssetsPath);

            //string webPlcPath = streamingAssetsPath + "/" + PlcConstant.WebPlcServerPath.TrimStart('/');
            //Debug.Log("webPlcPath:  " + webPlcPath);
            //string args = Uri.EscapeDataString(Application.streamingAssetsPath);
            //string finalUrl = webPlcPath + "?args=" + args;
            //Debug.Log("finalUrl:  " +finalUrl);

            //OpenExe(webPlcPath, streamingAssetsPath);
#endif
            ThreadUtility.Ins.CreateThread(async () =>
            {
                while (true)
                {
                    MainThreadTaskQueue.EnqueueTask(async () => 
                    {
                        await StartUpdateReadPlcValeAsync();
                    });
                    await Task.Delay(m_plcUpdateSpeed);
                }
            });

#endif
        }

        private void OnDestroy()
        {
            Process[] processes = Process.GetProcesses();
            foreach (Process process in processes)
            {
                try
                {
                    if (m_webPlcProcess != null && !process.HasExited && process.ProcessName == m_webPlcProcess.ProcessName)
                    {
                        process.Kill();
                    }
                }
                catch (InvalidOperationException ex)
                {
                    UnityEngine.Debug.Log(ex);
                }
            }
        }


        #region PLC函数

        /// <summary>
        /// 开始连接PLC
        /// </summary>
        /// <param name="plcName"></param>
        /// <param name="plcIp"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        public void StartConnectPlc(EPlcName plcName, string plcIp = "", Action succeedAction = null, Action errorAction = null)
        {
            if (!m_plcDic.ContainsKey(plcName))
            {
                errorAction?.Invoke();
                return;
            }
#if UNITY_WEBGL
            MSG_PlcConnect requestData = new MSG_PlcConnect
            {
                plcName = plcName,
                plcIp = plcIp,
                message = $"S2C->  PLC{plcName} --- {plcIp} 正在连接.."
            };

            string jsonData = JsonConvert.SerializeObject(requestData);
            StartCoroutine(PlcWebMessage.Ins.C2S_StartSendMessage(DESType.DEST_PlcConnect, PlcConstant.PlcConnectServerPath, jsonData, succeedAction, errorAction));
#else
            m_plcDic[plcName].StartConnect(plcIp, succeedAction, errorAction);
            m_plcDic[plcName].StartUpdateWritePlc();
#endif
        }

        /// <summary>
        /// 开始更新读取的Plc数值
        /// </summary>
        /// <returns></returns>
        private async Task StartUpdateReadPlcValeAsync()
        {
            foreach (var plc in m_plcDic)
            {
                if (plc.Value != null && await IsConnected(plc.Key))
                {
                    foreach (var valName in plc.Value.m_plcOutputList)
                    {
                        if (plc.Value.m_plcDic[valName].Value != null && !plc.Value.m_plcDic[valName].SingleWrite)
                        {
                            WriteValue(plc.Key, valName, plc.Value.m_plcDic[valName].Value);
                        }
                    }
                    Dictionary<string, object> newDic = await StartReadValue(plc.Key);
                    //foreach (var val in newDic)
                    //{
                    //    Debug.Log($"{plc.Key}接受的信息:{val.Key},{val.Value}");
                    //}
                    foreach (var valName in plc.Value.m_plcInputList)
                    {
                        if (plc.Value.m_plcDic.ContainsKey(valName))
                        {
                            plc.Value.m_plcDic[valName].Value = newDic[valName];
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 开始取消连接PLC
        /// </summary>
        /// <param name="plcName"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        public void StartDisConnectPlc(EPlcName plcName, Action succeedAction = null, Action errorAction = null)
        {
            if (!m_plcDic.ContainsKey(plcName))
            {
                errorAction?.Invoke();
                return;
            }

#if UNITY_WEBGL

            MSG_DisPlcConnect requestData = new MSG_DisPlcConnect
            {
                message = $"C2S->  PLC{plcName} --- 正在取消连接.."
            };
            string jsonData = JsonConvert.SerializeObject(requestData);
            StartCoroutine(PlcWebMessage.Ins.C2S_StartSendMessage(DESType.DEST_DisPlcConnect, PlcConstant.PlcConnectServerPath, jsonData, succeedAction, errorAction));
#else
            m_plcDic[plcName].StartDisConnect(succeedAction, errorAction);
#endif
        }

        /// <summary>
        /// 开始监听PLC值改变
        /// </summary>
        /// <param name="plcName"></param>
        /// <param name="valName"></param>
        /// <param name="action"></param>
        public void AddListenerPLCValueChange(EPlcName plcName, string valName, PlcValueChanged action)
        {
            if (!m_plcDic.ContainsKey(plcName))
            {
                return;
            }
            m_plcDic[plcName].AddListenerPLCValueChange(valName, action);
        }

        /// <summary>
        /// 移除PLC值改变
        /// </summary>
        /// <param name="plcName"></param>
        /// <param name="valName"></param>
        /// <param name="action"></param>
        public void RemoveListenerPLCValueChange(EPlcName plcName, string valName, PlcValueChanged action)
        {
            if (!m_plcDic.ContainsKey(plcName))
            {
                return;
            }
            m_plcDic[plcName].RemoveListenerPLCValueChange(valName, action);
        }

        /// <summary>
        /// 写入值
        /// </summary>
        /// <param name="valName"></param>
        /// <param name="val"></param>
        public void WriteValue(EPlcName plcName, string valName, object val, bool singleWrite = false)
        {
            if (!m_plcDic.ContainsKey(plcName))
            {
                return;
            }

#if UNITY_WEBGL
            MSG_WriteValue requestData = new MSG_WriteValue
            {
                plcName = plcName,
                valName = valName,
                val = val,
                singleWrite = singleWrite,
                message = $"C2S->  PLC:{plcName} ---Val:{valName}"
            };
            string jsonData = JsonConvert.SerializeObject(requestData);
            StartCoroutine(PlcWebMessage.Ins.C2S_StartSendMessage(DESType.DEST_WriteValue, PlcConstant.PlcConnectServerPath, jsonData));
#else
            m_plcDic[plcName].WriteValue(valName, val, singleWrite);
#endif
        }

        /// <summary>
        /// 开始读取数值
        /// </summary>
        /// <param name="plcName"></param>
        /// <returns></returns>
        private async Task<Dictionary<string, object>> StartReadValue(EPlcName plcName)
        {
#if UNITY_WEBGL
            MSG_ReadValue requestData = new MSG_ReadValue
            {
                plcName = plcName,
                message = $"C2S->  PLC{plcName} --- 正在读取数据.."
            };
            MSG_Type msg_Type = new MSG_Type
            {
                desType = DESType.DEST_ReadValue,
                content = JsonConvert.SerializeObject(requestData),
            };
            string jsonData = JsonConvert.SerializeObject(msg_Type);
            return await PlcWebMessage.Ins.C2S_ReadValue(PlcConstant.PlcConnectServerPath, jsonData);
#endif
            return null;
        }

        /// <summary>
        /// 是否连接
        /// </summary>
        /// <param name="plcName"></param>
        /// <returns></returns>
        public async Task<bool> IsConnected(EPlcName plcName)
        {
            if (!m_plcDic.ContainsKey(plcName))
            {
                return false;
            }

#if UNITY_WEBGL
            MSG_IsConnected requestData = new MSG_IsConnected
            {
                plcName = plcName,
                message = $"C2S->  PLC:{plcName}"
            };
            MSG_Type msg_Type = new MSG_Type
            {
                desType = DESType.DEST_IsConnected,
                content = JsonConvert.SerializeObject(requestData),
            };
            string jsonData = JsonConvert.SerializeObject(msg_Type);
            return await PlcWebMessage.Ins.C2S_IsConnected(DESType.DEST_IsConnected, PlcConstant.PlcConnectServerPath, jsonData);
#else
            return m_plcDic[plcName].IsConnected();
#endif
        }

        /// <summary>
        /// 读取配置文件
        /// </summary>
        /// <param name="csvPath"></param>
        /// <returns></returns>
        public async Task<Dictionary<string, MDataItem>> ReadPLCData(string csvPath)
        {
            csvPath = Application.streamingAssetsPath + csvPath;
#if UNITY_WEBGL && !UNITY_EDITOR
    csvPath = csvPath.Replace("file://", ""); 
#endif
            return await PlcCSVUtility.ReadPLCData(csvPath);
        }
        #endregion

        private IEnumerator WaitForServer()
        {
            bool isServerRunning = false;

            while (!isServerRunning)
            {
                using (UnityWebRequest request = UnityWebRequest.Get("http://localhost:5001"))
                {
                    yield return request.SendWebRequest(); 

                    if (request.result == UnityWebRequest.Result.Success)
                    {
                        isServerRunning = true;
                        Debug.Log("Server is running. Proceeding with exe start.");
                        string exePath = Application.streamingAssetsPath + PlcConstant.WebPlcServerPath;  
                        string arg1 = Application.streamingAssetsPath + PlcConstant.WebPlcServerPath;
                        string arg2 = Application.streamingAssetsPath;
                        string[] exeArgs = new string[] { arg1, arg2 }; 
                        StartCoroutine(StartWebServerHelperExe(exePath, exeArgs));
                    }
                    else
                    {
                        Debug.Log("Waiting for server to start...");
                        yield return new WaitForSeconds(1);  
                    }
                }
            }
        }

        /// <summary>
        /// 开始webserver辅助程序
        /// </summary>
        /// <param name="exePath"></param>
        /// <returns></returns>
        private IEnumerator StartWebServerHelperExe(string exePath, string[] exeArgs)
        {
            string parametersJson = string.Join(",", exeArgs.Select(arg => "\"" + arg + "\""));
            string jsonBody = "{\"ExePath\":\"" + exePath + "\", \"Parameters\":[" + parametersJson + "]}";

            using (UnityWebRequest request = new UnityWebRequest(PlcConstant.WebPlcServerHelperUrl, "POST"))
            {
                byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody);
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                request.downloadHandler = new DownloadHandlerBuffer();
                request.SetRequestHeader("Content-Type", "application/json");

                yield return request.SendWebRequest();

                if (request.result == UnityWebRequest.Result.Success)
                {
                    Debug.Log("Exe started successfully with parameters!");
                }
                else
                {
                    Debug.LogError("Failed to start exe: " + request.error);
                }
            }
        }
    }
}

2.PlcVariable

封装所有Plc内部功能

using S7.Net;
using System;
using static WebPlc.Scripts.MDataItem;

namespace WebPlc.Scripts
{
    public class MDataItem
    {
        public string Id = "";
        public string Name = "";
        public DataType DataType = DataType.Input;
        public VarType VarType = VarType.Byte;
        public string LogicalAddress = "";
        public bool SingleWrite = false;
        private object? m_value = null;
        private readonly object m_lock = new object();
        public event PlcValueChanged? OnMyValueChanged = null;

        public MDataItem() { }

        public object Value
        {
            get
            {
                lock (m_lock)
                {
                    return m_value;
                }
            }
            set
            {
                if (value == null)
                {
                    return;
                }

                lock (m_lock)
                {
                    if (!value.Equals(m_value))
                    {
                        m_value = value;
                        OnMyValueChanged?.Invoke(m_value);
                    }
                }
            }
        }

        public delegate void PlcValueChanged(object sender);
        public void SpecialTrigger()
        {
            OnMyValueChanged?.Invoke(m_value);
        }
    }
    internal class PlcVariable
    {
        private Plc m_plc = null;
        private string m_plcIp = "192.168.0.1";
        private CpuType m_plcType = CpuType.S71200;
        private short m_plcJiJiaNumber = 0;
        private short m_plcJiCaoNumber = 1;
        private int m_plcUpdateSpeed = 100;
        private int m_plcWriteSpeed = 1000;
        private bool m_plcUpdateState = true;
        public readonly Dictionary<string, MDataItem> m_plcDic = new Dictionary<string, MDataItem>();
        public readonly List<string> m_plcInputList = new List<string>();
        public readonly List<string> m_plcOutputList = new List<string>();

        public PlcVariable(Dictionary<string, MDataItem> plcDic, int plcUpdateSpeed = 100)
        {
            m_plcDic.Clear();
            m_plcInputList.Clear();
            m_plcOutputList.Clear();

            m_plcUpdateSpeed = plcUpdateSpeed;

            m_plcDic = plcDic;
            InitDataItemList();
        }



        public async Task StartConnect(string ip, Action succeedAction = null, Action errorAction = null)
        {
            try
            {
                Console.WriteLine("PLC准备连接IP:   " + ip);
                if (!string.IsNullOrEmpty(ip))
                {
                    m_plcIp = ip;
                    Console.WriteLine($"当前PLC尝试开始连接PLC:  {m_plcType},    {m_plcIp},     {m_plcJiJiaNumber},    {m_plcJiCaoNumber}");
                    m_plc = new Plc(m_plcType, m_plcIp, m_plcJiJiaNumber, m_plcJiCaoNumber);
                }
                else
                {
                    Console.WriteLine($"当前输入IP为空,尝试开始连接默认IP,PLC:  {m_plcType},    {m_plcIp},     {m_plcJiJiaNumber},    {m_plcJiCaoNumber}");
                    m_plc = new Plc(m_plcType, m_plcIp, m_plcJiJiaNumber, m_plcJiCaoNumber);
                }

                await m_plc.OpenAsync().ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        Task.Delay(100).ContinueWith(_ =>
                        {
                            errorAction?.Invoke();
                        });
                        Console.WriteLine("连接PLC失败");
                    }
                    else if (t.IsCompleted)
                    {
                        Task.Delay(100).ContinueWith(_ =>
                        {
                            succeedAction?.Invoke();
                        });
                        SetPlcUpdateState(true);
                        ThreadUtility.Ins.CreateThread(() =>
                        {
                            while (true)
                            {
                                UpdateReadeValue();
                                Thread.Sleep(m_plcUpdateSpeed);
                            }
                        });

                        Console.WriteLine("连接PLC成功");
                    }
                    Console.WriteLine("连接回调:  " + t.Exception);
                });
            }
            catch (System.Exception e)
            {
                errorAction?.Invoke();
                Console.WriteLine("PLC连接报错:" + e);
            }
        }

        /// <summary>
        /// 开始取消连接
        /// </summary>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        public void StartDisConnect(Action succeedAction = null, Action errorAction = null)
        {
            try
            {
                if (m_plc != null && m_plc.IsConnected)
                {
                    m_plc.Close();
                    succeedAction?.Invoke();
                }
                else
                {
                    errorAction?.Invoke();
                }
            }
            catch (Exception e)
            {
                errorAction?.Invoke();
                throw e;
            }
        }

        /// <summary>
        /// 初始化DataItem列表
        /// </summary>
        private void InitDataItemList()
        {
            foreach (var item in m_plcDic.Values)
            {
                if (item.DataType == DataType.Input)
                {
                    m_plcInputList.Add(item.Id + "_" + item.Name);
                }
                else
                {
                    m_plcOutputList.Add(item.Id + "_" + item.Name);
                }
            }
        }

        /// <summary>
        /// 更新读取值
        /// </summary>
        private async void UpdateReadeValue()
        {
            if (m_plcUpdateState && m_plc != null && m_plc.IsConnected)
            {
                foreach (var plcName in m_plcInputList)
                {
                    if (m_plcDic.ContainsKey(plcName))
                    {
                        m_plcDic[plcName].Value = await m_plc.ReadAsync(m_plcDic[plcName].LogicalAddress);
                    }
                }
            }
        }

        /// <summary>
        /// 得到值
        /// </summary>
        /// <param name="valName"></param>
        /// <returns></returns>
        public object GetValue(string valName)
        {
            if (m_plc != null && m_plc.IsConnected && m_plcDic.ContainsKey(valName))
            {
                return m_plcDic[valName].Value;
            }
            return null;
        }

        /// <summary>
        /// 写入值
        /// </summary>
        /// <param name="valName"></param>
        /// <param name="val"></param>
        public void WriteValue(string valName, object val, bool singleWrite = false)
        {
            if (m_plcDic.ContainsKey(valName))
            {
                m_plcDic[valName].Value = val;
                m_plcDic[valName].SingleWrite = singleWrite;
                if (m_plc != null && m_plc.IsConnected)
                {
                    string address = m_plcDic[valName].LogicalAddress;

                    switch (m_plcDic[valName].VarType)
                    {
                        case VarType.Bit:
                            break;
                        case VarType.Byte:
                            //Console.WriteLine($"开始{address}  写  {valName}  值: {(bool)val}");
                            m_plc.WriteAsync(address, Convert.ToBoolean(val));
                            break;
                        case VarType.Word:
                            break;
                        case VarType.DWord:
                            break;
                        case VarType.Int:
                            m_plc.WriteAsync(address, Convert.ToInt16(val));
                            break;
                        case VarType.DInt:
                            break;
                        case VarType.Real:
                            //m_plc.WriteAsync(address, Conversion.ConvertToFloat((uint)val));
                            try
                            {
                                m_plc.WriteAsync(address, Convert.ToSingle(val));
                            }
                            catch (Exception)
                            {
                                Console.WriteLine($"报错:m_plc.WriteAsync(address, (float)val):{val}");
                            }
                           
                            break;
                        case VarType.LReal:
                            break;
                        case VarType.String:
                            break;
                        case VarType.S7String:
                            break;
                        case VarType.S7WString:
                            break;
                        case VarType.Timer:
                            break;
                        case VarType.Counter:
                            break;
                        case VarType.DateTime:
                            break;
                        case VarType.DateTimeLong:
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// 设置Plc更新
        /// </summary>
        /// <param name="state"></param>
        private void SetPlcUpdateState(bool state)
        {
            m_plcUpdateState = state;
        }

        /// <summary>
        /// 开始监听数据变化
        /// </summary>
        /// <param name="valName"></param>
        /// <param name="action"></param>
        public void AddListenerPLCValueChange(string valName, PlcValueChanged action)
        {
            if (m_plcDic.ContainsKey(valName))
            {
                m_plcDic[valName].OnMyValueChanged += action;
                //Console.WriteLine($"当前监听变量:{valName}");
            }
        }

        /// <summary>
        /// 结束监听数据变化
        /// </summary>
        /// <param name="valName"></param>
        /// <param name="action"></param>
        public void RemoveListenerPLCValueChange(string valName, PlcValueChanged action)
        {
            if (m_plcDic.ContainsKey(valName))
            {
                m_plcDic[valName].OnMyValueChanged -= action;
            }
        }

        /// <summary>
        /// 特殊触发只读数据
        /// </summary>
        public void TriggerAllReadValue()
        {
            if (m_plc != null && m_plc.IsConnected)
            {
                foreach (var plcName in m_plcInputList)
                {
                    m_plcDic[plcName].SpecialTrigger();
                }
            }
        }

        /// <summary>
        /// 开始更新输出数据
        /// </summary>
        public void StartUpdateWritePlc()
        {
            ThreadUtility.Ins.CreateThread(() => {
                while (true)
                {
                    if (m_plc != null && m_plc.IsConnected)
                    {
                        foreach (var plcName in m_plcOutputList)
                        {
                            if (m_plcDic[plcName].Value != null && !m_plcDic[plcName].SingleWrite)
                            {
                                WriteValue(plcName, m_plcDic[plcName].Value, m_plcDic[plcName].SingleWrite);
                                //Console.WriteLine($"持续写出值:{plcName}   {m_plcDic[plcName].Value}");
                            }
                        }
                    }
                    Thread.Sleep(m_plcWriteSpeed);
                }
            });

        }

        public bool IsConnected()
        {
            if (m_plc != null)
            {
                return m_plc.IsConnected;
            }
            return false;
        }

        public async Task<object?> ReadPlcDataAsync(string valName)
        {
            if (!m_plcDic.ContainsKey(valName))
            {
                return null;
            }

            return await m_plc.ReadAsync(m_plcDic[valName].LogicalAddress);
        }
    }
}

3.PlcWebMessage

Plc Webgl平台通讯代码

using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

namespace ATF
{
    [Serializable]
    public class MSG_Type
    {
        public DESType desType = DESType.DEST_IsConnected;
        public string content;
    }

    [Serializable]
    public class MSG_PlcConnect
    {
        public EPlcName plcName = EPlcName.EPN_JiXieShou;
        public string plcIp = "";   //Plc IP
        public bool plcState;       //Plc状态
        public string message = ""; //返回信息
    }

    [Serializable]
    public class MSG_DisPlcConnect
    {
        public EPlcName plcName = EPlcName.EPN_JiXieShou;
        public bool connectState;       //连接状态
        public string message = ""; //返回信息
    }

    [Serializable]
    public class MSG_AddListenerPLCValueChange
    {
        public EPlcName plcName = EPlcName.EPN_JiXieShou;
        public string valName = "";
        public string message = ""; //返回信息
    }

    [Serializable]
    public class MSG_WriteValue
    {
        public EPlcName plcName = EPlcName.EPN_JiXieShou;
        public string valName = "";
        public object val = "";
        public bool singleWrite = false;
        public string message = ""; //返回信息
    }

    [Serializable]
    public class MSG_IsConnected
    {
        public EPlcName plcName = EPlcName.EPN_JiXieShou;
        public bool isConnected ;
        public string message = ""; //返回信息
    }

    [Serializable]
    public class MSG_ReadValue
    {
        public EPlcName plcName = EPlcName.EPN_JiXieShou;
        public Dictionary<string, object> dataDic = new Dictionary<string, object>(); //返回信息
        public string message = "";
    }

    public class PlcWebMessage : CommonSingletonTemplate<PlcWebMessage>
    {
        public IEnumerator C2S_StartSendMessage(DESType desType,string url, string jsonData, Action succeedAction = null, Action errorAction = null)
        {
            using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
            {
                MSG_Type msg_Type = new MSG_Type()
                {
                    desType = desType,
                    content = jsonData,
                };
                string msg = JsonConvert.SerializeObject(msg_Type);
                byte[] bodyRaw = Encoding.UTF8.GetBytes(msg);
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                request.downloadHandler = new DownloadHandlerBuffer();
                request.SetRequestHeader("Content-Type", "application/json");
                yield return request.SendWebRequest();
                if (request.ATResult())
                {
                    string responseJson = request.downloadHandler.text;
                    MSG_Type processRequestType = JsonConvert.DeserializeObject<MSG_Type>(responseJson);
                    if (processRequestType != null)
                    {
                        switch (processRequestType.desType)
                        {
                            case DESType.DEST_PlcConnect:
                                C2S_PlcConnect(processRequestType.content, succeedAction, errorAction);
                                break;
                            case DESType.DEST_DisPlcConnect:
                                C2S_DisPlcConnect(processRequestType.content, succeedAction, errorAction);
                                break;
                            case DESType.DEST_AddListenerPLCValueChange:
                                //DES_AddListenerPLCValueChange(processRequestType.content);
                                break;
                            case DESType.DEST_RemoveListenerPLCValueChange:
                                //DES_RemoveListenerPLCValueChange(processRequestType.content);
                                break;
                            case DESType.DEST_WriteValue:
                                //DES_WriteValue(processRequestType.content);
                                break;
                            case DESType.DEST_IsConnected:
                                //C2S_IsConnected(responseJson, succeedAction, errorAction);
                                break;
                            default:
                                break;
                        }
                    }
                }
                else
                {
                    Debug.LogError($"Request请求失败: {request.error}");
                    errorAction?.Invoke();
                }
            }
        }


        /// <summary>
        /// 是否建立了连接
        /// </summary>
        /// <param name="desType"></param>
        /// <param name="url"></param>
        /// <param name="jsonData"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        /// <returns></returns>
        public async Task<bool> C2S_IsConnected(DESType desType, string url, string jsonData, Action succeedAction = null, Action errorAction = null)
        {
            using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
            {
                byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                request.downloadHandler = new DownloadHandlerBuffer();
                request.SetRequestHeader("Content-Type", "application/json");

                var operation = request.SendWebRequest();

                while (!operation.isDone)
                {
                    await Task.Yield(); 
                }

                if (request.ATResult())
                {
                    string responseJson = request.downloadHandler.text;
                    MSG_Type processRequestType = JsonConvert.DeserializeObject<MSG_Type>(responseJson);
                    try
                    {
                        if (processRequestType != null)
                        {
                            switch (processRequestType.desType)
                            {
                                case DESType.DEST_IsConnected:
                                    MSG_IsConnected responseData = JsonConvert.DeserializeObject<MSG_IsConnected>(processRequestType.content);
                                    return responseData.isConnected;
                                default:
                                    break;
                            }
                        }

                    }
                    catch (Exception ex)
                    {
                        Debug.LogError($"JSON 解析错误: {ex.Message}");
                    }
                }
                else
                {
                    Debug.LogError($"请求失败: {request.error}");
                }
            }

            return false; 
        }

        /// <summary>
        /// 读取值
        /// </summary>
        /// <param name="plcName"></param>
        /// <param name="url"></param>
        /// <param name="jsonData"></param>
        /// <returns></returns>
        public async Task<Dictionary<string, object>> C2S_ReadValue(string url, string jsonData)
        {
            using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
            {
                byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                request.downloadHandler = new DownloadHandlerBuffer();
                request.SetRequestHeader("Content-Type", "application/json");

                var operation = request.SendWebRequest();

                while (!operation.isDone)
                {
                    await Task.Yield(); 
                }

                if (request.ATResult()) 
                {
                    string responseJson = request.downloadHandler.text;
                    MSG_Type processRequestType = JsonConvert.DeserializeObject<MSG_Type>(responseJson);
                    try
                    {
                        if (processRequestType != null)
                        {
                            switch (processRequestType.desType)
                            {
                                case DESType.DEST_ReadValue:
                                    MSG_ReadValue responseData = JsonConvert.DeserializeObject<MSG_ReadValue>(processRequestType.content);
                                    //Debug.Log($"C2S_ReadValue Dic Count->{responseData.dataDic.Count}");
                                    return responseData.dataDic;
                                default:
                                    break;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.LogError($"JSON 解析错误: {ex.Message}");
                    }
                }
                else
                {
                    Debug.LogError($"请求失败: {request.error}");
                }
            }

            return null; // 失败时返回默认值
        }

        /// <summary>
        /// 消息_解析Plc连接
        /// </summary>
        /// <param name="request"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        private void C2S_PlcConnect(string content, Action succeedAction = null, Action errorAction = null)
        {
            MSG_PlcConnect responseData = JsonConvert.DeserializeObject<MSG_PlcConnect>(content);

            if (responseData.plcState)
            {
                Debug.Log($"服务器响应: {responseData.message},状态:{responseData.plcState}”");
                succeedAction?.Invoke();
            }
            else
            {
                Debug.LogError($"请求失败: {responseData.plcState}");
                errorAction?.Invoke();
            }
        }

        /// <summary>
        /// 消息_取消连接
        /// </summary>
        /// <param name="request"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        private void C2S_DisPlcConnect(string request, Action succeedAction = null, Action errorAction = null)
        {
            MSG_DisPlcConnect responseData = JsonConvert.DeserializeObject<MSG_DisPlcConnect>(request);

            if (responseData.connectState)
            {
                Debug.Log($"服务器响应: {responseData.message}");
                succeedAction?.Invoke();
            }
            else
            {
                Debug.LogError($"请求失败: {responseData.connectState}");
                errorAction?.Invoke();
            }
        }

        /// <summary>
        /// 消息_开始监听PLC值变化
        /// </summary>
        /// <param name="request"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        private void C2S_AddListenerPLCValueChange(string request, Action succeedAction = null, Action errorAction = null)
        {
            MSG_AddListenerPLCValueChange responseData = JsonConvert.DeserializeObject<MSG_AddListenerPLCValueChange>(request);

            //if (responseData.plcValueChangedState)
            //{
            //    Debug.Log($"服务器响应: {responseData.message}");
            //    succeedAction?.Invoke();
            //}
            //else
            //{
            //    Debug.LogError($"请求失败: {request.error}");
            //    errorAction?.Invoke();
            //}
        }

        /// <summary>
        /// 消息_开始移除PLC值变化
        /// </summary>
        /// <param name="request"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        private void C2S_RemoveListenerPLCValueChange(string request, Action succeedAction = null, Action errorAction = null)
        {
            MSG_AddListenerPLCValueChange responseData = JsonConvert.DeserializeObject<MSG_AddListenerPLCValueChange>(request);

            //if (responseData.plcValueChangedState)
            //{
            //    Debug.Log($"服务器响应: {responseData.message}");
            //    succeedAction?.Invoke();
            //}
            //else
            //{
            //    Debug.LogError($"请求失败: {request.error}");
            //    errorAction?.Invoke();
            //}
        }

        /// <summary>
        /// 消息_开始移除PLC值变化
        /// </summary>
        /// <param name="request"></param>
        /// <param name="succeedAction"></param>
        /// <param name="errorAction"></param>
        private void C2S_WriteValue(string request, Action succeedAction = null, Action errorAction = null)
        {
            MSG_WriteValue responseData = JsonConvert.DeserializeObject<MSG_WriteValue>(request);

            //if (responseData.plcValueChangedState)
            //{
            //    Debug.Log($"服务器响应: {responseData.message}");
            //    succeedAction?.Invoke();
            //}
            //else
            //{
            //    Debug.LogError($"请求失败: {request.error}");
            //    errorAction?.Invoke();
            //}
        }
    }
}

五、如何使用及注意事项

1.安装 Unity、TIA Portal V16、S7-PLCSIM Advanced V3.0
2.新建博图项目,配置好Plc点位
3.配置好 Assets/StreamingAssets/PlcConfig/Plc程序A.csv路径下的Plc点位
4.输入Plc 项目IP,点击连接
5.Web平台格外注意:先打开Assets/StreamingAssets/WebPlc/WebPlc.exe路径下的服务器,再进行连接操作。

总结

好啦,本文介绍的就这么多,有兴趣可以下载看看。Unity&Plc仿真项目,上手门槛比较低,但是前提是要有相关软件的使用基础,比如如何使用博图,如何使用S7 - PLCSIM Advanced V3.0,如何使用RobotStudio等。本文介绍的通信和管理Plc只是其中一环,实际项目中有很大一部分工作在于实现真实物理效果的仿真。后期如果真的去做仿真项目,希望本文实现的功能能给大家做个参考,其实直接在这个基础上开发新功能也是可以的,毕竟我封装的目的就是为了便于二次开发使用。后续这个项目会持续更新,会考虑将Abb相关的功能也加进来。喜欢的小伙伴可以点赞关注加收藏,你的支持是我更新的最大动力,有问题可以滴我~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值