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(); } }