568 lines
19 KiB
C#
568 lines
19 KiB
C#
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
using System;
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR || UNITY_ANDROID
|
||
using System.IO.Ports;
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// Modbus设备控制器,用于控制和管理Modbus设备的开关状态。
|
||
/// </summary>
|
||
public class ModbusDeviceController : MonoBehaviour
|
||
{
|
||
[System.Serializable]
|
||
public class ModbusDevice
|
||
{
|
||
public string deviceName = "未命名设备";
|
||
public ushort registerAddress = 0x0000;
|
||
public byte[] openCommand = new byte[8];
|
||
public byte[] closeCommand = new byte[8];
|
||
public bool isActive = false;
|
||
|
||
/// <summary>
|
||
/// 构造函数,用于创建Modbus设备实例。
|
||
/// </summary>
|
||
/// <param name="name"></param>
|
||
/// <param name="address"></param>
|
||
/// <param name="openHex"></param>
|
||
/// <param name="closeHex"></param>
|
||
public ModbusDevice(string name, ushort address, string openHex, string closeHex)
|
||
{
|
||
deviceName = name;
|
||
registerAddress = address;
|
||
openCommand = HexStringToByteArray(openHex);
|
||
closeCommand = HexStringToByteArray(closeHex);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将十六进制字符串转换为字节数组。
|
||
/// </summary>
|
||
/// <param name="hex"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="ArgumentException"></exception>
|
||
private static byte[] HexStringToByteArray(string hex)
|
||
{
|
||
hex = hex.Replace(" ", "").Replace("-", "").Trim();
|
||
|
||
if (hex.Length % 2 != 0)
|
||
{
|
||
throw new ArgumentException($"十六进制字符串长度必须为偶数: {hex}");
|
||
}
|
||
|
||
byte[] bytes = new byte[hex.Length / 2];
|
||
|
||
for (int i = 0; i < bytes.Length; i++)
|
||
{
|
||
string hexByte = hex.Substring(i * 2, 2);
|
||
bytes[i] = Convert.ToByte(hexByte, 16);
|
||
}
|
||
|
||
return bytes;
|
||
}
|
||
}
|
||
|
||
[Header("串口配置")]
|
||
[Tooltip("串口号,如COM3、COM4等")]
|
||
public string portName = "COM3";
|
||
[Range(9600, 115200)]
|
||
public int baudRate = 9600;
|
||
|
||
[Header("设备列表 - 共13个控制功能")]
|
||
public List<ModbusDevice> deviceList = new List<ModbusDevice>();
|
||
|
||
[Header("控制设置")]
|
||
[Tooltip("启用调试日志")]
|
||
public bool enableDebug = true;
|
||
[Tooltip("命令发送间隔(秒)")]
|
||
public float commandInterval = 0.1f;
|
||
|
||
[Header("设备状态")]
|
||
[Tooltip("显示设备当前状态")]
|
||
public List<string> deviceStatus = new List<string>();
|
||
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR || UNITY_ANDROID
|
||
private SerialPort serialPort;
|
||
#endif
|
||
|
||
private float lastCommandTime = 0f;
|
||
|
||
private void Start()
|
||
{
|
||
InitializeDeviceList();
|
||
InitializeDeviceStatus();
|
||
ScanSerialPorts();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化设备列表,根据实际需求添加或修改Modbus设备的配置。
|
||
/// </summary>
|
||
private void InitializeDeviceList()
|
||
{
|
||
deviceList.Clear();
|
||
|
||
// 第一组:管路控制(1-5路)
|
||
deviceList.Add(new ModbusDevice("第一路红色管路", 0x0000, "01 05 00 00 FF 00 8C 3A", "01 05 00 00 00 00 CD CA"));
|
||
deviceList.Add(new ModbusDevice("第二路绿色管路", 0x0001, "01 05 00 01 FF 00 DD FA", "01 05 00 01 00 00 9C 0A"));
|
||
deviceList.Add(new ModbusDevice("第三路蓝色管路", 0x0002, "01 05 00 02 FF 00 2D FA", "01 05 00 02 00 00 6C 0A"));
|
||
deviceList.Add(new ModbusDevice("第四路罐体", 0x0003, "01 05 00 03 FF 00 7C 3A", "01 05 00 03 00 00 3D CA"));
|
||
deviceList.Add(new ModbusDevice("第五路照明", 0x0004, "01 05 00 04 FF 00 CD FB", "01 05 00 04 00 00 8C 0B"));
|
||
|
||
// 第二组:设备控制(6-12路,注意地址0-4与上面重复)
|
||
deviceList.Add(new ModbusDevice("第一路空压机转动", 0x0000, "01 05 00 00 FF 00 8C 3A", "01 05 00 00 00 00 CD CA"));
|
||
deviceList.Add(new ModbusDevice("第二路控制柜亮", 0x0001, "01 05 00 01 FF 00 DD FA", "01 05 00 01 00 00 9C 0A"));
|
||
deviceList.Add(new ModbusDevice("第三路红色管路流动", 0x0002, "01 05 00 02 FF 00 2D FA", "01 05 00 02 00 00 6C 0A"));
|
||
deviceList.Add(new ModbusDevice("第四路第一个罐上升", 0x0003, "01 05 00 03 FF 00 7C 3A", "01 05 00 03 00 00 3D CA"));
|
||
deviceList.Add(new ModbusDevice("第五路蓝色管路流动", 0x0004, "01 05 00 04 FF 00 CD FB", "01 05 00 04 00 00 8C 0B"));
|
||
deviceList.Add(new ModbusDevice("第六路第二个罐亮", 0x0005, "01 05 00 05 FF 00 9C 3B", "01 05 00 05 00 00 DD CB"));
|
||
deviceList.Add(new ModbusDevice("第七路第二个汽轮机工作", 0x0006, "01 05 00 06 FF 00 6C 3B", "01 05 00 06 00 00 2D CB"));
|
||
|
||
if (enableDebug)
|
||
{
|
||
Debug.Log($"设备列表初始化完成,共 {deviceList.Count} 个控制功能");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化设备状态列表,确保每个设备的初始状态为"未连接"。
|
||
/// </summary>
|
||
private void InitializeDeviceStatus()
|
||
{
|
||
deviceStatus.Clear();
|
||
for (int i = 0; i < deviceList.Count; i++)
|
||
{
|
||
deviceStatus.Add("未连接");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 扫描并列出所有可用的串口。
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public bool OpenSerialPort()
|
||
{
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR || UNITY_ANDROID
|
||
try
|
||
{
|
||
if (serialPort != null && serialPort.IsOpen)
|
||
{
|
||
if (enableDebug) Debug.Log($"串口 {portName} 已打开");
|
||
return true;
|
||
}
|
||
|
||
serialPort = new SerialPort(portName, baudRate)
|
||
{
|
||
DataBits = 8,
|
||
Parity = Parity.None,
|
||
StopBits = StopBits.One,
|
||
Handshake = Handshake.None,
|
||
ReadTimeout = 1000,
|
||
WriteTimeout = 1000
|
||
};
|
||
|
||
serialPort.Open();
|
||
|
||
if (enableDebug)
|
||
{
|
||
Debug.Log($"串口 {portName} 打开成功");
|
||
Debug.Log($"串口配置: 波特率={baudRate}, 8N1");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (enableDebug) Debug.LogError($"打开串口失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
#else
|
||
if (enableDebug) Debug.LogWarning("当前平台不支持System.IO.Ports,使用模拟模式");
|
||
return true;
|
||
#endif
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送设备命令到串口。
|
||
/// </summary>
|
||
/// <param name="deviceIndex"></param>
|
||
/// <param name="turnOn"></param>
|
||
/// <returns></returns>
|
||
public bool SendDeviceCommand(int deviceIndex, bool turnOn)
|
||
{
|
||
if (deviceIndex < 0 || deviceIndex >= deviceList.Count)
|
||
{
|
||
if (enableDebug) Debug.LogError($"设备索引 {deviceIndex} 无效");
|
||
return false;
|
||
}
|
||
|
||
if (Time.time - lastCommandTime < commandInterval)
|
||
{
|
||
if (enableDebug) Debug.LogWarning($"命令发送过于频繁,请等待 {commandInterval:F2} 秒");
|
||
return false;
|
||
}
|
||
|
||
ModbusDevice device = deviceList[deviceIndex];
|
||
byte[] command = turnOn ? device.openCommand : device.closeCommand;
|
||
|
||
if (enableDebug)
|
||
{
|
||
string action = turnOn ? "打开" : "关闭";
|
||
string hexStr = ByteArrayToHexString(command);
|
||
Debug.Log($"[{device.deviceName}] {action}");
|
||
Debug.Log($"指令: {hexStr}");
|
||
}
|
||
|
||
bool success = SendSerialData(command);
|
||
|
||
if (success)
|
||
{
|
||
deviceList[deviceIndex].isActive = turnOn;
|
||
deviceStatus[deviceIndex] = turnOn ? "已打开" : "已关闭";
|
||
lastCommandTime = Time.time;
|
||
}
|
||
else
|
||
{
|
||
deviceStatus[deviceIndex] = "发送失败";
|
||
}
|
||
|
||
return success;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换指定设备的状态(打开/关闭)。如果设备已开启,则关闭;反之,则开启。
|
||
/// </summary>
|
||
/// <param name="deviceIndex"></param>
|
||
public void ToggleDevice(int deviceIndex)
|
||
{
|
||
if (deviceIndex >= 0 && deviceIndex < deviceList.Count)
|
||
{
|
||
bool newState = !deviceList[deviceIndex].isActive;
|
||
SendDeviceCommand(deviceIndex, newState);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过设备名称控制设备的开关状态。如果找到匹配的设备,则发送命令;否则,记录错误信息。
|
||
/// </summary>
|
||
/// <param name="deviceName"></param>
|
||
/// <param name="turnOn"></param>
|
||
public void ControlDeviceByName(string deviceName, bool turnOn)
|
||
{
|
||
for (int i = 0; i < deviceList.Count; i++)
|
||
{
|
||
if (deviceList[i].deviceName == deviceName)
|
||
{
|
||
SendDeviceCommand(i, turnOn);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (enableDebug) Debug.LogError($"未找到设备: {deviceName}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开所有设备。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
public void TurnOnAllDevices()
|
||
{
|
||
if (enableDebug) Debug.Log("打开所有设备");
|
||
|
||
for (int i = 0; i < deviceList.Count; i++)
|
||
{
|
||
SendDeviceCommand(i, true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭所有设备。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
public void TurnOffAllDevices()
|
||
{
|
||
if (enableDebug) Debug.Log("关闭所有设备");
|
||
|
||
for (int i = 0; i < deviceList.Count; i++)
|
||
{
|
||
SendDeviceCommand(i, false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开指定设备组。如果启用调试,则记录日志信息。如果结束索引超出范围,只处理到列表末尾为止。
|
||
/// </summary>
|
||
/// <param name="startIndex"></param>
|
||
/// <param name="endIndex"></param>
|
||
public void TurnOnGroup(int startIndex, int endIndex)
|
||
{
|
||
if (enableDebug) Debug.Log($"打开设备组 {startIndex}-{endIndex}");
|
||
|
||
for (int i = startIndex; i <= endIndex && i < deviceList.Count; i++)
|
||
{
|
||
SendDeviceCommand(i, true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭指定设备组。如果启用调试,则记录日志信息。如果结束索引超出范围,只处理到列表末尾为止。
|
||
/// </summary>
|
||
/// <param name="startIndex"></param>
|
||
/// <param name="endIndex"></param>
|
||
public void TurnOffGroup(int startIndex, int endIndex)
|
||
{
|
||
if (enableDebug) Debug.Log($"关闭设备组 {startIndex}-{endIndex}");
|
||
|
||
for (int i = startIndex; i <= endIndex && i < deviceList.Count; i++)
|
||
{
|
||
SendDeviceCommand(i, false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 控制第一路红色管路
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlRedPipe(bool on) => ControlDeviceByName("第一路红色管路", on);
|
||
/// <summary>
|
||
/// 控制第二路绿色管路
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlGreenPipe(bool on) => ControlDeviceByName("第二路绿色管路", on);
|
||
/// <summary>
|
||
/// 控制第三路蓝色管路
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlBluePipe(bool on) => ControlDeviceByName("第三路蓝色管路", on);
|
||
/// <summary>
|
||
/// 控制第四路罐体
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlTank(bool on) => ControlDeviceByName("第四路罐体", on);
|
||
/// <summary>
|
||
/// 控制第五路照明
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlLighting(bool on) => ControlDeviceByName("第五路照明", on);
|
||
/// <summary>
|
||
/// 控制第一路空压机转动
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlAirCompressor(bool on) => ControlDeviceByName("第一路空压机转动", on);
|
||
/// <summary>
|
||
/// 控制第二路控制柜亮
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlCabinetLight(bool on) => ControlDeviceByName("第二路控制柜亮", on);
|
||
/// <summary>
|
||
/// 控制第三路红色管路流动
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlRedPipeFlow(bool on) => ControlDeviceByName("第三路红色管路流动", on);
|
||
/// <summary>
|
||
/// 控制第四路第一个罐上升
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlTankUp(bool on) => ControlDeviceByName("第四路第一个罐上升", on);
|
||
/// <summary>
|
||
/// 控制第五路蓝色管路流动
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlBluePipeFlow(bool on) => ControlDeviceByName("第五路蓝色管路流动", on);
|
||
/// <summary>
|
||
/// 控制第六路第二个罐亮
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlSecondTankLight(bool on) => ControlDeviceByName("第六路第二个罐亮", on);
|
||
/// <summary>
|
||
/// 控制第七路第二个汽轮机工作
|
||
/// </summary>
|
||
/// <param name="on"></param>
|
||
public void ControlSecondTurbine(bool on) => ControlDeviceByName("第七路第二个汽轮机工作", on);
|
||
|
||
/// <summary>
|
||
/// 发送设备命令
|
||
/// </summary>
|
||
/// <param name="data"></param>
|
||
/// <returns></returns>
|
||
private bool SendSerialData(byte[] data)
|
||
{
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR || UNITY_ANDROID
|
||
if (serialPort == null || !serialPort.IsOpen)
|
||
{
|
||
if (!OpenSerialPort())
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
serialPort.Write(data, 0, data.Length);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (enableDebug) Debug.LogError($"发送数据失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
#else
|
||
if (enableDebug) Debug.Log("模拟发送: 成功");
|
||
return true;
|
||
#endif
|
||
}
|
||
|
||
/// <summary>
|
||
/// 扫描可用串口。只在调试模式下执行,用于测试和验证。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
private void ScanSerialPorts()
|
||
{
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR || UNITY_ANDROID
|
||
try
|
||
{
|
||
string[] ports = SerialPort.GetPortNames();
|
||
if (ports.Length > 0)
|
||
{
|
||
Debug.Log($"可用串口: {string.Join(", ", ports)}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("未找到可用串口");
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
if (enableDebug) Debug.LogError("扫描串口失败");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前设备控制状态的总览字符串。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public string GetStatusSummary()
|
||
{
|
||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||
sb.AppendLine("=== 设备控制状态总结 ===");
|
||
sb.AppendLine($"总共 {deviceList.Count} 个控制功能");
|
||
sb.AppendLine();
|
||
|
||
// 管路控制组
|
||
sb.AppendLine("【管路控制组】");
|
||
for (int i = 0; i < 5; i++)
|
||
{
|
||
sb.AppendLine($" {i + 1}. {deviceList[i].deviceName}: {deviceStatus[i]}");
|
||
}
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("【设备控制组】");
|
||
for (int i = 5; i < deviceList.Count; i++)
|
||
{
|
||
sb.AppendLine($" {i - 4}. {deviceList[i].deviceName}: {deviceStatus[i]}");
|
||
}
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine($"串口: {portName}, 波特率: {baudRate}");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打印当前设备控制状态的总览字符串。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
public void PrintStatus()
|
||
{
|
||
if (enableDebug)
|
||
{
|
||
Debug.Log(GetStatusSummary());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试所有命令,依次打开每个设备。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
public void TestAllCommands()
|
||
{
|
||
if (enableDebug) Debug.Log("开始测试所有命令...");
|
||
|
||
for (int i = 0; i < deviceList.Count; i++)
|
||
{
|
||
if (enableDebug) Debug.Log($"测试设备 {i + 1}: {deviceList[i].deviceName}");
|
||
SendDeviceCommand(i, true);
|
||
}
|
||
|
||
if (enableDebug) Debug.Log("所有命令测试完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将字节数组转换为十六进制字符串格式。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
/// <param name="bytes"></param>
|
||
/// <returns></returns>
|
||
private string ByteArrayToHexString(byte[] bytes)
|
||
{
|
||
return BitConverter.ToString(bytes).Replace("-", " ");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开串口并初始化。如果启用调试,则记录日志信息。
|
||
/// </summary>
|
||
public void CloseSerialPort()
|
||
{
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_EDITOR || UNITY_ANDROID
|
||
if (serialPort != null && serialPort.IsOpen)
|
||
{
|
||
serialPort.Close();
|
||
if (enableDebug) Debug.Log("串口已关闭");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
private void OnApplicationQuit()
|
||
{
|
||
CloseSerialPort();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
CloseSerialPort();
|
||
}
|
||
}
|
||
|
||
// UI控制示例
|
||
public class DeviceControlUI : MonoBehaviour
|
||
{
|
||
public ModbusDeviceController controller;
|
||
|
||
// Unity UI按钮可以绑定这些方法
|
||
public void OpenAllDevices()
|
||
{
|
||
if (controller != null)
|
||
{
|
||
controller.TurnOnAllDevices();
|
||
}
|
||
}
|
||
|
||
public void CloseAllDevices()
|
||
{
|
||
if (controller != null)
|
||
{
|
||
controller.TurnOffAllDevices();
|
||
}
|
||
}
|
||
|
||
public void ControlRedPipeOn() => controller.ControlRedPipe(true);
|
||
public void ControlRedPipeOff() => controller.ControlRedPipe(false);
|
||
public void ControlGreenPipeOn() => controller.ControlGreenPipe(true);
|
||
public void ControlGreenPipeOff() => controller.ControlGreenPipe(false);
|
||
public void ControlBluePipeOn() => controller.ControlBluePipe(true);
|
||
public void ControlBluePipeOff() => controller.ControlBluePipe(false);
|
||
public void ControlAirCompressorOn() => controller.ControlAirCompressor(true);
|
||
public void ControlAirCompressorOff() => controller.ControlAirCompressor(false);
|
||
|
||
[ContextMenu("显示设备状态")]
|
||
public void ShowStatus()
|
||
{
|
||
if (controller != null)
|
||
{
|
||
controller.PrintStatus();
|
||
}
|
||
}
|
||
} |