Unity开发记录_12_PC / Web工业仿真项目 使用S7.Net 和 博图TIA Portal V16 和 S7-PLCSIM Advanced V3.0 的开源项目(二)_unity 交换机仿真项目
文章目录
- 前言
- 一、Unity Plc 工业仿真项目是什么?
-
- 为什么要做PLC工业仿真?
- Unity 如何与 PLC 进行通信?
- 二、效果展示
-
- 1.PC端通信效果
- 2.Web端通信效果
- 三、项目地址
- 四、主要代码介绍
-
- 1.PlcManager
- 2.PlcVariable
- 3.PlcWebMessage
- 五、如何使用及注意事项
- 总结
前言
本项目结合了 S7.Net、TIA Portal V16 和 S7-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{ /// /// 3个不同的plc程序--示例 这个地方可以后期更改 /// public enum EPlcName { EPN_FenJian, EPN_JiXieShou, EPN_CangKu, } /// /// web端通讯的消息类型 /// public enum DESType { DEST_PlcConnect, //Plc 连接 DEST_DisPlcConnect, //Plc 取消连接 DEST_AddListenerPLCValueChange, //监听Plc数据变化 DEST_RemoveListenerPLCValueChange, //移除Plc数据监听 DEST_WriteValue, //写入数值 DEST_IsConnected, //是否连接 DEST_ReadValue, //读取数值 } /// /// Plc管理单例,所有Plc相关的操作 都从这调用 /// 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函数 /// /// 开始连接PLC /// /// /// /// /// 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 } /// /// 开始更新读取的Plc数值 /// /// 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]; } } } } } /// /// 开始取消连接PLC /// /// /// /// 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 } /// /// 开始监听PLC值改变 /// /// /// /// public void AddListenerPLCValueChange(EPlcName plcName, string valName, PlcValueChanged action) { if (!m_plcDic.ContainsKey(plcName)) { return; } m_plcDic[plcName].AddListenerPLCValueChange(valName, action); } /// /// 移除PLC值改变 /// /// /// /// public void RemoveListenerPLCValueChange(EPlcName plcName, string valName, PlcValueChanged action) { if (!m_plcDic.ContainsKey(plcName)) { return; } m_plcDic[plcName].RemoveListenerPLCValueChange(valName, action); } /// /// 写入值 /// /// /// 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 } /// /// 开始读取数值 /// /// /// 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; } /// /// 是否连接 /// /// /// 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 } /// /// 读取配置文件 /// /// /// 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);} } } } /// /// 开始webserver辅助程序 /// /// /// 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); } } /// /// 开始取消连接 /// /// /// 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; } } /// /// 初始化DataItem列表 /// 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); } } } /// /// 更新读取值 /// 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); } } } } /// /// 得到值 /// /// /// public object GetValue(string valName) { if (m_plc != null && m_plc.IsConnected && m_plcDic.ContainsKey(valName)) { return m_plcDic[valName].Value; } return null; } /// /// 写入值 /// /// /// 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; } } } } /// /// 设置Plc更新 /// /// private void SetPlcUpdateState(bool state) { m_plcUpdateState = state; } /// /// 开始监听数据变化 /// /// /// public void AddListenerPLCValueChange(string valName, PlcValueChanged action) { if (m_plcDic.ContainsKey(valName)) { m_plcDic[valName].OnMyValueChanged += action; //Console.WriteLine($\"当前监听变量:{valName}\"); } } /// /// 结束监听数据变化 /// /// /// public void RemoveListenerPLCValueChange(string valName, PlcValueChanged action) { if (m_plcDic.ContainsKey(valName)) { m_plcDic[valName].OnMyValueChanged -= action; } } /// /// 特殊触发只读数据 /// public void TriggerAllReadValue() { if (m_plc != null && m_plc.IsConnected) { foreach (var plcName in m_plcInputList) { m_plcDic[plcName].SpecialTrigger(); } } } /// /// 开始更新输出数据 /// 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(); } } } /// /// 是否建立了连接 /// /// /// /// /// /// /// 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; } /// /// 读取值 /// /// /// /// /// 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; // 失败时返回默认值 } /// /// 消息_解析Plc连接 /// /// /// /// 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(); } } /// /// 消息_取消连接 /// /// /// /// 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(); } } /// /// 消息_开始监听PLC值变化 /// /// /// /// 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(); //} } /// /// 消息_开始移除PLC值变化 /// /// /// /// 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(); //} } /// /// 消息_开始移除PLC值变化 /// /// /// /// 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相关的功能也加进来。喜欢的小伙伴可以点赞关注加收藏,你的支持是我更新的最大动力,有问题可以滴我~