using System.IO.Ports;
using System.Threading;
using UnityEngine;
using System.Collections.Generic;
///
/// Modbus RTU 控制脚本
///
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 devices = new List()
{
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();
}
}