文章目录
前言
本项目结合了 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
{
/// <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相关的功能也加进来。喜欢的小伙伴可以点赞关注加收藏,你的支持是我更新的最大动力,有问题可以滴我~