EnergyEfficiencyManagement/Assets/Zion/Scripts/Utils/ModbusDeviceController.cs

568 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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