317 lines
9.6 KiB
C#
317 lines
9.6 KiB
C#
using System.IO.Ports;
|
||
using System.Threading;
|
||
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
|
||
/// <summary>
|
||
/// Modbus RTU 控制脚本
|
||
/// </summary>
|
||
public class ModbusMultiDeviceController : MonoBehaviour
|
||
{
|
||
[System.Serializable]
|
||
public class DeviceInfo
|
||
{
|
||
public string deviceName; // 设备名称
|
||
public ushort registerAddress; // 寄存器地址
|
||
public bool isOn; // 当前状态
|
||
}
|
||
|
||
[Header("串口配置")]
|
||
public string portName = "COM3";
|
||
public int baudRate = 9600;
|
||
public Parity parity = Parity.None;
|
||
public int dataBits = 8;
|
||
public StopBits stopBits = StopBits.One;
|
||
|
||
[Header("设备列表")]
|
||
public List<DeviceInfo> devices = new List<DeviceInfo>()
|
||
{
|
||
new DeviceInfo { deviceName = "第一路红色管路", registerAddress = 0x0000, isOn = false },
|
||
new DeviceInfo { deviceName = "第二路绿色管路", registerAddress = 0x0001, isOn = false },
|
||
new DeviceInfo { deviceName = "第三路蓝色管路", registerAddress = 0x0002, isOn = false },
|
||
new DeviceInfo { deviceName = "第四路罐体", registerAddress = 0x0003, isOn = false },
|
||
new DeviceInfo { deviceName = "第五路照明", registerAddress = 0x0004, isOn = false },
|
||
new DeviceInfo { deviceName = "第六路第二个罐亮", registerAddress = 0x0005, isOn = false },
|
||
new DeviceInfo { deviceName = "第七路第二个汽轮机工作", registerAddress = 0x0006, isOn = false }
|
||
};
|
||
|
||
private SerialPort serialPort;
|
||
private byte deviceAddress = 0x01; // 设备地址
|
||
private byte functionCode = 0x05; // 功能码:写单个线圈
|
||
|
||
// 初始化串口
|
||
public bool OpenPort()
|
||
{
|
||
if (serialPort != null && serialPort.IsOpen)
|
||
{
|
||
Debug.LogWarning("串口已经打开。");
|
||
return true;
|
||
}
|
||
|
||
try
|
||
{
|
||
serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
|
||
serialPort.ReadTimeout = 500;
|
||
serialPort.WriteTimeout = 500;
|
||
serialPort.Open();
|
||
Debug.Log($"串口 {portName} 打开成功,波特率:{baudRate}");
|
||
return true;
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"打开串口失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 计算 Modbus RTU 的 CRC16 校验码 (低位在前)
|
||
public static byte[] CalculateCRC(byte[] data, int length)
|
||
{
|
||
ushort crc = 0xFFFF;
|
||
for (int i = 0; i < length; i++)
|
||
{
|
||
crc ^= data[i];
|
||
for (int j = 0; j < 8; j++)
|
||
{
|
||
if ((crc & 0x0001) != 0)
|
||
{
|
||
crc >>= 1;
|
||
crc ^= 0xA001;
|
||
}
|
||
else
|
||
{
|
||
crc >>= 1;
|
||
}
|
||
}
|
||
}
|
||
return new byte[] { (byte)(crc & 0xFF), (byte)((crc >> 8) & 0xFF) };
|
||
}
|
||
|
||
// 构建完整指令帧
|
||
public byte[] BuildCommand(ushort registerAddress, bool turnOn)
|
||
{
|
||
ushort value = turnOn ? (ushort)0xFF00 : (ushort)0x0000;
|
||
|
||
byte[] frame = new byte[8];
|
||
frame[0] = deviceAddress;
|
||
frame[1] = functionCode;
|
||
frame[2] = (byte)(registerAddress >> 8);
|
||
frame[3] = (byte)(registerAddress & 0xFF);
|
||
frame[4] = (byte)(value >> 8);
|
||
frame[5] = (byte)(value & 0xFF);
|
||
|
||
byte[] crc = CalculateCRC(frame, 6);
|
||
frame[6] = crc[0];
|
||
frame[7] = crc[1];
|
||
|
||
return frame;
|
||
}
|
||
|
||
// 发送指令到指定设备
|
||
public bool SendCommand(int deviceIndex, bool turnOn)
|
||
{
|
||
if (deviceIndex < 0 || deviceIndex >= devices.Count)
|
||
{
|
||
Debug.LogError($"设备索引 {deviceIndex} 无效,有效范围:0-{devices.Count - 1}");
|
||
return false;
|
||
}
|
||
|
||
if (serialPort == null || !serialPort.IsOpen)
|
||
{
|
||
Debug.LogError("串口未打开,请先调用 OpenPort()");
|
||
return false;
|
||
}
|
||
|
||
DeviceInfo device = devices[deviceIndex];
|
||
byte[] command = BuildCommand(device.registerAddress, turnOn);
|
||
|
||
try
|
||
{
|
||
serialPort.DiscardInBuffer();
|
||
serialPort.Write(command, 0, command.Length);
|
||
Debug.Log($"[{device.deviceName}] 发送指令: {(turnOn ? "打开" : "关闭")} - {ByteArrayToString(command)}");
|
||
|
||
// 等待设备响应
|
||
Thread.Sleep(50);
|
||
|
||
// 读取响应
|
||
int bytesToRead = serialPort.BytesToRead;
|
||
if (bytesToRead > 0)
|
||
{
|
||
byte[] response = new byte[bytesToRead];
|
||
int bytesRead = serialPort.Read(response, 0, bytesToRead);
|
||
|
||
// 验证响应
|
||
if (bytesRead == command.Length && ByteArraysEqual(command, response))
|
||
{
|
||
device.isOn = turnOn;
|
||
Debug.Log($"[{device.deviceName}] {(turnOn ? "打开" : "关闭")} 成功");
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[{device.deviceName}] 响应验证失败");
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[{device.deviceName}] 设备无响应");
|
||
return false;
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"[{device.deviceName}] 通信错误: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 通过设备名称控制
|
||
public bool SendCommandByName(string deviceName, bool turnOn)
|
||
{
|
||
int deviceIndex = devices.FindIndex(d => d.deviceName == deviceName);
|
||
if (deviceIndex == -1)
|
||
{
|
||
Debug.LogError($"未找到设备: {deviceName}");
|
||
return false;
|
||
}
|
||
return SendCommand(deviceIndex, turnOn);
|
||
}
|
||
|
||
// 控制所有设备
|
||
public void TurnAllOn()
|
||
{
|
||
Debug.Log("打开所有设备...");
|
||
for (int i = 0; i < devices.Count; i++)
|
||
{
|
||
SendCommand(i, true);
|
||
Thread.Sleep(100); // 设备间间隔
|
||
}
|
||
}
|
||
|
||
public void TurnAllOff()
|
||
{
|
||
Debug.Log("关闭所有设备...");
|
||
for (int i = devices.Count - 1; i >= 0; i--)
|
||
{
|
||
SendCommand(i, false);
|
||
Thread.Sleep(100); // 设备间间隔
|
||
}
|
||
}
|
||
|
||
// 切换设备状态
|
||
public void ToggleDevice(int deviceIndex)
|
||
{
|
||
if (deviceIndex >= 0 && deviceIndex < devices.Count)
|
||
{
|
||
DeviceInfo device = devices[deviceIndex];
|
||
SendCommand(deviceIndex, !device.isOn);
|
||
}
|
||
}
|
||
|
||
// 获取设备状态
|
||
public bool GetDeviceStatus(int deviceIndex)
|
||
{
|
||
if (deviceIndex >= 0 && deviceIndex < devices.Count)
|
||
{
|
||
return devices[deviceIndex].isOn;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public bool GetDeviceStatusByName(string deviceName)
|
||
{
|
||
DeviceInfo device = devices.Find(d => d.deviceName == deviceName);
|
||
return device != null ? device.isOn : false;
|
||
}
|
||
|
||
// 工具方法
|
||
private string ByteArrayToString(byte[] bytes)
|
||
{
|
||
return System.BitConverter.ToString(bytes).Replace("-", " ");
|
||
}
|
||
|
||
private bool ByteArraysEqual(byte[] a, byte[] b)
|
||
{
|
||
if (a.Length != b.Length) return false;
|
||
for (int i = 0; i < a.Length; i++)
|
||
{
|
||
if (a[i] != b[i]) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 测试功能 - 按顺序控制所有设备
|
||
public void TestAllDevices()
|
||
{
|
||
if (!OpenPort())
|
||
{
|
||
Debug.LogError("无法打开串口,测试中止");
|
||
return;
|
||
}
|
||
|
||
Debug.Log("开始测试所有设备...");
|
||
StartCoroutine(TestSequence());
|
||
}
|
||
|
||
private System.Collections.IEnumerator TestSequence()
|
||
{
|
||
// 打开所有设备
|
||
for (int i = 0; i < devices.Count; i++)
|
||
{
|
||
SendCommand(i, true);
|
||
yield return new WaitForSeconds(1.0f); // 等待1秒
|
||
}
|
||
|
||
yield return new WaitForSeconds(2.0f); // 等待2秒
|
||
|
||
// 关闭所有设备
|
||
for (int i = devices.Count - 1; i >= 0; i--)
|
||
{
|
||
SendCommand(i, false);
|
||
yield return new WaitForSeconds(1.0f); // 等待1秒
|
||
}
|
||
|
||
Debug.Log("设备测试完成");
|
||
}
|
||
|
||
// 直接控制特定设备(快捷方法)
|
||
public void ControlRedPipe(bool turnOn) => SendCommandByName("第一路红色管路", turnOn);
|
||
public void ControlGreenPipe(bool turnOn) => SendCommandByName("第二路绿色管路", turnOn);
|
||
public void ControlBluePipe(bool turnOn) => SendCommandByName("第三路蓝色管路", turnOn);
|
||
public void ControlTank(bool turnOn) => SendCommandByName("第四路罐体", turnOn);
|
||
public void ControlLighting(bool turnOn) => SendCommandByName("第五路照明", turnOn);
|
||
public void ControlSecondTankLight(bool turnOn) => SendCommandByName("第六路第二个罐亮", turnOn);
|
||
public void ControlSecondTurbine(bool turnOn) => SendCommandByName("第七路第二个汽轮机工作", turnOn);
|
||
|
||
// 关闭串口
|
||
public void ClosePort()
|
||
{
|
||
if (serialPort != null && serialPort.IsOpen)
|
||
{
|
||
TurnAllOff(); // 先关闭所有设备
|
||
Thread.Sleep(500);
|
||
serialPort.Close();
|
||
Debug.Log("串口已关闭");
|
||
}
|
||
}
|
||
|
||
// Unity 生命周期
|
||
private void Start()
|
||
{
|
||
// 自动打开串口
|
||
OpenPort();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
ClosePort();
|
||
}
|
||
|
||
private void OnApplicationQuit()
|
||
{
|
||
ClosePort();
|
||
}
|
||
}
|