DCS/ruiyiweiUX/Assets/Scripts/Communication/DCSSerialManager.cs

886 lines
31 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 System;
// using System.Collections.Generic;
// using UnityEngine;
// using System.Threading;
// using GeneralTools;
// namespace DCX.Communication
// {
// /// <summary>
// /// DCS项目串口通信管理器
// /// 基于DCS项目串口通信协议说明书实现
// /// 支持安卓平台的串口通信
// /// </summary>
// public class DCSSerialManager : MonoBehaviour
// {
// #region 协议常量
// // 帧结构常量
// private const byte FRAME_HEADER_1 = 0xAA;
// private const byte FRAME_HEADER_2 = 0x55;
// private const byte FRAME_TAIL_1 = 0x0D;
// private const byte FRAME_TAIL_2 = 0x0A;
// // 命令字定义(基于协议文档)
// public enum CommandType : byte
// {
// HandshakeResponse = 0x01, // 握手应答 + 时间同步(下→上)
// HandshakeRequest = 0x02, // 握手请求(上→下)
// HeartbeatReport = 0x03, // 心跳上报(上→下)
// HeartbeatAck = 0x04, // 心跳应答(下→上)
// AlarmControl = 0x05, // 报警控制命令(下→上)
// DataReport = 0x06, // 数据上报(上→下)
// ParameterQuery = 0x07, // 参数查询或设置(下→上)
// AlarmFeedback = 0x08, // 告警状态反馈(上→下)
// TimeSync = 0x09 // 时间同步命令(下→上)
// }
// // 报警代码定义
// public enum AlarmCode : byte
// {
// BatteryLow40 = 0x11, // 电池电量低(<40%
// LaserTempAbnormal = 0x22, // 激光器温度异常
// BatteryLow20 = 0x21, // 电池电量低(<20%
// BatteryEmpty5 = 0x31, // 电池电量空(<5%
// BFIAbnormal = 0x32, // BFI数值异常
// LaserWorkAbnormal = 0x33, // 激光器工作异常
// CommAbnormal = 0x34, // 通信异常
// BatteryFault = 0x35, // 电池故障
// ACNotConnected = 0x12 // 交流电未连接
// }
// #endregion
// #region 数据结构
// [Serializable]
// public struct HeartbeatData
// {
// public float bfiValue; // BFI血流值
// public byte batteryLevel; // 电池电量 0-100%
// public ushort batteryVoltage; // 电池电压 mV
// public ushort temperature; // 内部温度 0.1°C
// public byte laserStatus; // 激光器状态 0=异常,1=工作,2=停止
// public byte powerType; // 电源类型 0=AC,1=BAT
// public uint reserved; // 保留字段
// }
// [Serializable]
// public struct AlarmCommand
// {
// public AlarmCode alarmCode; // 报警代码
// public byte priority; // 报警级别 0=低,1=中,2=高
// public byte status; // 报警状态 0=清除,1=触发
// }
// #endregion
// #region 事件定义
// public event Action OnConnected;
// public event Action OnDisconnected;
// public event Action<HeartbeatData> OnHeartbeatReceived;
// public event Action<AlarmCommand> OnAlarmReceived;
// public event Action<string> OnError;
// public event Action<byte[]> OnRawDataReceived;
// #endregion
// #region 私有字段
// private ISerialPortAdapter serialPort;
// private Thread readThread;
// private bool isRunning = false;
// private Queue<byte[]> sendQueue = new Queue<byte[]>();
// private object lockObject = new object();
// private float lastHeartbeatTime = 0f;
// private float lastDataReportTime = 0f;
// private bool isHandshakeCompleted = false;
// // 配置参数从AndroidConfigHelper加载
// private string portName;
// private int baudRate;
// private int heartbeatInterval;
// private int communicationTimeout;
// private bool autoReconnect;
// private int maxReconnectAttempts;
// #endregion
// #region Unity生命周期
// void Start()
// {
// LoadConfiguration();
// InitializeSerialPort();
// }
// void Update()
// {
// ProcessSendQueue();
// CheckHeartbeat();
// // CheckDataReport();
// }
// void OnDestroy()
// {
// Disconnect();
// }
// #endregion
// #region 配置加载
// private void LoadConfiguration()
// {
// // 根据平台设置默认串口名称
// string defaultPortName;
// #if UNITY_ANDROID && !UNITY_EDITOR
// defaultPortName = "/dev/ttyS4";
// #elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
// defaultPortName = "COM1";
// #elif UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX
// defaultPortName = "/dev/ttyS4";
// #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
// defaultPortName = "/dev/cu.usbserial-1410";
// #else
// defaultPortName = "COM1";
// #endif
// portName = AndroidConfigHelper.GetConfig("serialPortName", "SerialPort", defaultPortName);
// baudRate = AndroidConfigHelper.GetConfig("serialBaudRate", "SerialPort", 115200);
// heartbeatInterval = AndroidConfigHelper.GetConfig("heartbeatInterval", "SerialPort", 1000);
// communicationTimeout = AndroidConfigHelper.GetConfig("communicationTimeout", "SerialPort", 5000);
// autoReconnect = AndroidConfigHelper.GetConfig("autoReconnect", "SerialPort", true);
// maxReconnectAttempts = AndroidConfigHelper.GetConfig("maxReconnectAttempts", "SerialPort", 5);
// Debug.Log($"DCS串口配置加载完成: {portName}, {baudRate}bps (平台: {Application.platform})");
// }
// #endregion
// #region 串口初始化和连接
// private void InitializeSerialPort()
// {
// try
// {
// #if UNITY_ANDROID && !UNITY_EDITOR
// // 安卓平台使用JNI调用原生串口
// serialPort = new AndroidSerialPortAdapter();
// #elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
// // Windows平台使用真实串口
// serialPort = new WindowsSerialPortAdapter();
// Debug.Log("DCS使用Windows真实串口");
// #elif UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX
// // Linux平台使用真实串口
// serialPort = new WindowsSerialPortAdapter(); // 复用Windows适配器
// Debug.Log("DCS使用Linux真实串口");
// #else
// // 其他平台使用模拟器
// serialPort = new SimulatedSerialPortAdapter();
// Debug.LogWarning("DCS使用串口模拟器未支持平台");
// #endif
// Connect();
// }
// catch (Exception e)
// {
// Debug.LogError($"DCS串口初始化失败: {e.Message}");
// OnError?.Invoke($"串口初始化失败: {e.Message}");
// }
// }
// public void Connect()
// {
// try
// {
// if (serialPort != null && serialPort.Open(portName, baudRate))
// {
// isRunning = true;
// StartReadThread();
// SendHandshakeRequest();
// OnConnected?.Invoke();
// Debug.Log("串口连接成功");
// }
// else
// {
// OnError?.Invoke("串口连接失败");
// }
// }
// catch (Exception e)
// {
// OnError?.Invoke($"连接异常: {e.Message}");
// }
// }
// public void Disconnect()
// {
// isRunning = false;
// if (readThread != null && readThread.IsAlive)
// {
// readThread.Join(1000); // 等待读取线程结束
// }
// serialPort?.Close();
// OnDisconnected?.Invoke();
// Debug.Log("串口已断开");
// }
// #endregion
// #region 数据发送和接收
// private void StartReadThread()
// {
// readThread = new Thread(ReadDataThread)
// {
// IsBackground = true
// };
// readThread.Start();
// }
// private void ReadDataThread()
// {
// byte[] buffer = new byte[1024];
// while (isRunning)
// {
// try
// {
// if (serialPort != null && serialPort.IsOpen)
// {
// int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
// if (bytesRead > 0)
// {
// byte[] data = new byte[bytesRead];
// Array.Copy(buffer, 0, data, 0, bytesRead);
// // 在主线程处理数据
// MainThreadDispatcher.Enqueue(() => ProcessReceivedData(data));
// }
// }
// }
// catch (Exception e)
// {
// MainThreadDispatcher.Enqueue(() => OnError?.Invoke($"读取数据异常: {e.Message}"));
// }
// Thread.Sleep(10); // 避免过度占用CPU
// }
// }
// private void ProcessReceivedData(byte[] data)
// {
// OnRawDataReceived?.Invoke(data);
// // 解析协议帧
// for (int i = 0; i < data.Length - 6; i++) // 最小帧长度检查
// {
// if (data[i] == FRAME_HEADER_1 && data[i + 1] == FRAME_HEADER_2)
// {
// if (TryParseFrame(data, i, out var frame))
// {
// ProcessFrame(frame);
// }
// }
// }
// }
// private bool TryParseFrame(byte[] data, int startIndex, out ProtocolFrame frame)
// {
// frame = new ProtocolFrame();
// try
// {
// if (startIndex + 6 >= data.Length) return false;
// frame.Command = (CommandType)data[startIndex + 2];
// frame.DataLength = data[startIndex + 3];
// int totalFrameLength = 4 + frame.DataLength + 3; // 帧头+命令+长度+数据+校验+帧尾
// if (startIndex + totalFrameLength > data.Length) return false;
// frame.Data = new byte[frame.DataLength];
// Array.Copy(data, startIndex + 4, frame.Data, 0, frame.DataLength);
// frame.Checksum = data[startIndex + 4 + frame.DataLength];
// // 验证校验和
// byte calculatedChecksum = CalculateChecksum(data, startIndex + 2, 2 + frame.DataLength);
// if (calculatedChecksum != frame.Checksum)
// {
// Debug.LogWarning("校验和不匹配");
// return false;
// }
// // 验证帧尾
// if (data[startIndex + 5 + frame.DataLength] != FRAME_TAIL_1 ||
// data[startIndex + 6 + frame.DataLength] != FRAME_TAIL_2)
// {
// Debug.LogWarning("帧尾不匹配");
// return false;
// }
// return true;
// }
// catch (Exception e)
// {
// Debug.LogError($"解析帧失败: {e.Message}");
// return false;
// }
// }
// private void ProcessFrame(ProtocolFrame frame)
// {
// switch (frame.Command)
// {
// case CommandType.HeartbeatReport:
// ProcessHeartbeatReport(frame.Data);
// SendHeartbeatAck();
// break;
// case CommandType.DataReport:
// ProcessDataReport(frame.Data);
// break;
// case CommandType.AlarmFeedback:
// ProcessAlarmFeedback(frame.Data);
// break;
// case CommandType.HandshakeRequest:
// ProcessHandshakeRequest(frame.Data);
// break;
// default:
// Debug.Log($"收到未处理的命令: {frame.Command}");
// break;
// }
// }
// private void ProcessHeartbeatReport(byte[] data)
// {
// if (data.Length >= 15) // HeartbeatData结构体大小
// {
// HeartbeatData heartbeat = new HeartbeatData
// {
// bfiValue = BitConverter.ToSingle(data, 0),
// batteryLevel = data[4],
// batteryVoltage = BitConverter.ToUInt16(data, 5),
// temperature = BitConverter.ToUInt16(data, 7),
// laserStatus = data[9],
// powerType = data[10],
// reserved = BitConverter.ToUInt32(data, 11)
// };
// OnHeartbeatReceived?.Invoke(heartbeat);
// lastHeartbeatTime = Time.time;
// }
// }
// private void ProcessDataReport(byte[] data)
// {
// // 处理数据上报,格式与心跳报告相同
// ProcessHeartbeatReport(data);
// lastDataReportTime = Time.time;
// }
// private void ProcessAlarmFeedback(byte[] data)
// {
// if (data.Length >= 3)
// {
// AlarmCommand alarm = new AlarmCommand
// {
// alarmCode = (AlarmCode)data[0],
// priority = data[1],
// status = data[2]
// };
// OnAlarmReceived?.Invoke(alarm);
// }
// }
// private void ProcessHandshakeRequest(byte[] data)
// {
// Debug.Log("收到握手请求,发送时间同步应答");
// SendHandshakeResponse();
// isHandshakeCompleted = true;
// }
// #endregion
// #region 协议命令发送
// public void SendHandshakeRequest()
// {
// byte[] data = { 0x01 }; // 固件版本
// SendFrame(CommandType.HandshakeRequest, data);
// }
// public void SendHandshakeResponse()
// {
// DateTime now = DateTime.Now;
// byte[] data = {
// (byte)(now.Year % 100),
// (byte)now.Month,
// (byte)now.Day,
// (byte)now.Hour,
// (byte)now.Minute,
// (byte)now.Second
// };
// SendFrame(CommandType.HandshakeResponse, data);
// }
// public void SendHeartbeatAck()
// {
// byte[] data = { 0x00 }; // 简单应答
// SendFrame(CommandType.HeartbeatAck, data);
// }
// public void SendAlarmControl(AlarmCode alarmCode, byte priority, byte status)
// {
// byte[] data = { (byte)alarmCode, priority, status };
// SendFrame(CommandType.AlarmControl, data);
// }
// private void SendFrame(CommandType command, byte[] data)
// {
// try
// {
// List<byte> frame = new List<byte>();
// // 帧头
// frame.Add(FRAME_HEADER_1);
// frame.Add(FRAME_HEADER_2);
// // 命令字
// frame.Add((byte)command);
// // 数据长度
// frame.Add((byte)data.Length);
// // 数据
// frame.AddRange(data);
// // 校验和
// byte checksum = CalculateChecksum(frame.ToArray(), 2, 2 + data.Length);
// frame.Add(checksum);
// // 帧尾
// frame.Add(FRAME_TAIL_1);
// frame.Add(FRAME_TAIL_2);
// var frameArray = frame.ToArray();
// var hexString = BitConverter.ToString(frameArray).Replace("-", " ");
// Debug.Log($"[DCS发送队列] {GetCommandName(command)}: {hexString}");
// lock (lockObject)
// {
// sendQueue.Enqueue(frameArray);
// }
// }
// catch (Exception e)
// {
// Debug.LogError($"DCS发送帧失败: {e.Message}");
// OnError?.Invoke($"发送帧失败: {e.Message}");
// }
// }
// private string GetCommandName(CommandType command)
// {
// return command switch
// {
// CommandType.HandshakeResponse => "握手应答",
// CommandType.HandshakeRequest => "握手请求",
// CommandType.HeartbeatReport => "心跳上报",
// CommandType.HeartbeatAck => "心跳应答",
// CommandType.AlarmControl => "报警控制",
// CommandType.DataReport => "数据上报",
// CommandType.ParameterQuery => "参数查询",
// CommandType.AlarmFeedback => "报警反馈",
// CommandType.TimeSync => "时间同步",
// _ => $"未知命令({(byte)command:X2})"
// };
// }
// private void ProcessSendQueue()
// {
// lock (lockObject)
// {
// while (sendQueue.Count > 0)
// {
// byte[] frame = sendQueue.Dequeue();
// try
// {
// if (serialPort != null && serialPort.IsOpen)
// {
// serialPort.Write(frame, 0, frame.Length);
// var hexString = BitConverter.ToString(frame).Replace("-", " ");
// Debug.Log($"[DCS串口发送] {hexString}");
// }
// else
// {
// Debug.LogError("DCS串口未打开无法发送数据");
// }
// }
// catch (Exception e)
// {
// Debug.LogError($"DCS串口写入失败: {e.Message}");
// OnError?.Invoke($"串口写入失败: {e.Message}");
// }
// }
// }
// }
// #endregion
// #region 辅助方法
// private byte CalculateChecksum(byte[] data, int offset, int length)
// {
// byte checksum = 0;
// for (int i = offset; i < offset + length; i++)
// {
// checksum ^= data[i];
// }
// return checksum;
// }
// private void CheckHeartbeat()
// {
// if (isHandshakeCompleted && Time.time - lastHeartbeatTime > communicationTimeout / 1000f)
// {
// Debug.LogWarning("心跳超时");
// if (autoReconnect)
// {
// Reconnect();
// }
// }
// }
// private void CheckDataReport()
// {
// // 检查数据上报间隔
// float dataReportInterval = AndroidConfigHelper.GetConfig("dataReportInterval", "SerialPort", 1000) / 1000f;
// if (Time.time - lastDataReportTime > dataReportInterval * 2)
// {
// Debug.LogWarning("数据上报超时");
// }
// }
// private void Reconnect()
// {
// Debug.Log("尝试重连...");
// Disconnect();
// Invoke(nameof(Connect), 2f); // 2秒后重连
// }
// #endregion
// #region 内部数据结构
// private struct ProtocolFrame
// {
// public CommandType Command;
// public byte DataLength;
// public byte[] Data;
// public byte Checksum;
// }
// #endregion
// }
// #region 串口适配器接口
// public interface ISerialPortAdapter
// {
// bool Open(string portName, int baudRate);
// void Close();
// bool IsOpen { get; }
// int Read(byte[] buffer, int offset, int count);
// void Write(byte[] buffer, int offset, int count);
// }
// // Windows平台串口适配器使用System.IO.Ports
// public class WindowsSerialPortAdapter : ISerialPortAdapter
// {
// #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX
// private System.IO.Ports.SerialPort serialPort;
// public bool IsOpen => serialPort?.IsOpen ?? false;
// public bool Open(string portName, int baudRate)
// {
// try
// {
// serialPort = new System.IO.Ports.SerialPort(portName, baudRate, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One)
// {
// ReadTimeout = 1000,
// WriteTimeout = 1000
// };
// serialPort.Open();
// Debug.Log($"Windows串口打开成功: {portName} @ {baudRate}");
// return true;
// }
// catch (Exception e)
// {
// Debug.LogError($"Windows串口打开失败: {e.Message}");
// return false;
// }
// }
// public void Close()
// {
// try
// {
// if (serialPort?.IsOpen == true)
// {
// serialPort.Close();
// serialPort.Dispose();
// serialPort = null;
// Debug.Log("Windows串口已关闭");
// }
// }
// catch (Exception e)
// {
// Debug.LogError($"Windows串口关闭失败: {e.Message}");
// }
// }
// public int Read(byte[] buffer, int offset, int count)
// {
// try
// {
// if (serialPort != null && serialPort.IsOpen && serialPort.BytesToRead > 0)
// {
// return serialPort.Read(buffer, offset, count);
// }
// return 0;
// }
// catch (Exception e)
// {
// Debug.LogError($"Windows串口读取失败: {e.Message}");
// return 0;
// }
// }
// public void Write(byte[] buffer, int offset, int count)
// {
// try
// {
// if (serialPort != null && serialPort.IsOpen)
// {
// serialPort.Write(buffer, offset, count);
// }
// }
// catch (Exception e)
// {
// Debug.LogError($"Windows串口写入失败: {e.Message}");
// }
// }
// #else
// public bool IsOpen => false;
// public bool Open(string portName, int baudRate) { Debug.LogWarning("WindowsSerialPortAdapter不支持此平台"); return false; }
// public void Close() { }
// public int Read(byte[] buffer, int offset, int count) { return 0; }
// public void Write(byte[] buffer, int offset, int count) { }
// #endif
// }
// // 安卓平台串口适配器需要JNI实现
// public class AndroidSerialPortAdapter : ISerialPortAdapter
// {
// private AndroidJavaObject serialPortJNI;
// public bool IsOpen { get; private set; }
// public bool Open(string portName, int baudRate)
// {
// try
// {
// #if UNITY_ANDROID && !UNITY_EDITOR
// serialPortJNI = new AndroidJavaObject("com.dcx.serialport.SerialPortHelper");
// bool result = serialPortJNI.Call<bool>("openSerialPort", portName, baudRate);
// IsOpen = result;
// return result;
// #else
// return false;
// #endif
// }
// catch (Exception e)
// {
// Debug.LogError($"Android串口打开失败: {e.Message}");
// return false;
// }
// }
// public void Close()
// {
// try
// {
// #if UNITY_ANDROID && !UNITY_EDITOR
// serialPortJNI?.Call("closeSerialPort");
// #endif
// IsOpen = false;
// }
// catch (Exception e)
// {
// Debug.LogError($"Android串口关闭失败: {e.Message}");
// }
// }
// public int Read(byte[] buffer, int offset, int count)
// {
// try
// {
// #if UNITY_ANDROID && !UNITY_EDITOR
// byte[] data = serialPortJNI.Call<byte[]>("readData", count);
// if (data != null && data.Length > 0)
// {
// Array.Copy(data, 0, buffer, offset, Math.Min(data.Length, count));
// return data.Length;
// }
// #endif
// return 0;
// }
// catch (Exception e)
// {
// Debug.LogError($"Android串口读取失败: {e.Message}");
// return 0;
// }
// }
// public void Write(byte[] buffer, int offset, int count)
// {
// try
// {
// #if UNITY_ANDROID && !UNITY_EDITOR
// byte[] data = new byte[count];
// Array.Copy(buffer, offset, data, 0, count);
// serialPortJNI.Call("writeData", data);
// #endif
// }
// catch (Exception e)
// {
// Debug.LogError($"Android串口写入失败: {e.Message}");
// }
// }
// }
// // 模拟串口适配器(用于测试)
// public class SimulatedSerialPortAdapter : ISerialPortAdapter
// {
// public bool IsOpen { get; private set; }
// private Queue<byte> simulatedData = new Queue<byte>();
// private System.Random random = new System.Random();
// public bool Open(string portName, int baudRate)
// {
// IsOpen = true;
// Debug.Log($"模拟串口已打开: {portName}@{baudRate}");
// return true;
// }
// public void Close()
// {
// IsOpen = false;
// Debug.Log("模拟串口已关闭");
// }
// public int Read(byte[] buffer, int offset, int count)
// {
// // 模拟接收心跳数据
// if (simulatedData.Count == 0 && random.NextDouble() < 0.1) // 10%概率生成数据
// {
// GenerateSimulatedHeartbeat();
// }
// int bytesRead = 0;
// while (simulatedData.Count > 0 && bytesRead < count)
// {
// buffer[offset + bytesRead] = simulatedData.Dequeue();
// bytesRead++;
// }
// return bytesRead;
// }
// public void Write(byte[] buffer, int offset, int count)
// {
// string hex = BitConverter.ToString(buffer, offset, count);
// Debug.Log($"模拟串口发送: {hex}");
// }
// private void GenerateSimulatedHeartbeat()
// {
// // 生成模拟心跳帧
// List<byte> frame = new List<byte>
// {
// 0xAA, 0x55, // 帧头
// 0x03, // 心跳报告命令
// 0x0F, // 数据长度15字节
// };
// // BFI值 (float)
// byte[] bfiBytes = BitConverter.GetBytes(50.0f + (float)(random.NextDouble() - 0.5) * 20);
// frame.AddRange(bfiBytes);
// // 电池电量
// frame.Add((byte)(80 + random.Next(0, 20)));
// // 电池电压 (mV)
// byte[] voltageBytes = BitConverter.GetBytes((ushort)(3700 + random.Next(0, 500)));
// frame.AddRange(voltageBytes);
// // 温度
// byte[] tempBytes = BitConverter.GetBytes((ushort)(250 + random.Next(0, 100))); // 25.0-35.0°C
// frame.AddRange(tempBytes);
// // 激光器状态
// frame.Add(0x01); // 工作中
// // 电源类型
// frame.Add(0x01); // 电池供电
// // 保留字段
// frame.AddRange(new byte[4]);
// // 校验和
// byte checksum = 0;
// for (int i = 2; i < frame.Count; i++)
// {
// checksum ^= frame[i];
// }
// frame.Add(checksum);
// // 帧尾
// frame.Add(0x0D);
// frame.Add(0x0A);
// foreach (byte b in frame)
// {
// simulatedData.Enqueue(b);
// }
// }
// }
// // 主线程调度器(用于在主线程处理串口数据)
// public class MainThreadDispatcher : MonoBehaviour
// {
// private static MainThreadDispatcher instance;
// private static Queue<Action> executionQueue = new Queue<Action>();
// public static void Enqueue(Action action)
// {
// lock (executionQueue)
// {
// executionQueue.Enqueue(action);
// }
// }
// void Awake()
// {
// if (instance == null)
// {
// instance = this;
// DontDestroyOnLoad(gameObject);
// }
// else
// {
// Destroy(gameObject);
// }
// }
// void Update()
// {
// lock (executionQueue)
// {
// while (executionQueue.Count > 0)
// {
// executionQueue.Dequeue().Invoke();
// }
// }
// }
// }
// #endregion
// }