406 lines
15 KiB
C#
406 lines
15 KiB
C#
using SimpleLite.RCS;
|
|
using SimpleLite.RCS.CarTypes;
|
|
using SimpleCore;
|
|
using SimpleCore.Library;
|
|
using SimpleCore.PropType;
|
|
using StandardScene.Charge;
|
|
using StandardScene.TCP;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace StandardScene.ChargeStationType
|
|
{
|
|
public class FLChargeStation : AbstractChargeStation
|
|
{
|
|
private AsyncTcpClient Client;
|
|
private IPEndPoint _endPoint;
|
|
|
|
// 重连相关
|
|
private Timer _reconnectTimer;
|
|
private bool _isManualDisconnect = false; // 标识是否为手动断开
|
|
private int _reconnectAttempts = 0; // 重连尝试次数
|
|
private const int MAX_RECONNECT_ATTEMPTS = 5; // 最大重连次数
|
|
private const int RECONNECT_INTERVAL_MS = 3000; // 重连间隔(毫秒)
|
|
private readonly object _connectionLock = new object(); // 连接锁
|
|
private bool _isConnecting = false; // 是否正在连接中
|
|
|
|
// 连接状态
|
|
public bool IsConnected { get; private set; } = false;
|
|
public DateTime? LastConnectedTime { get; private set; }
|
|
public DateTime? LastDisconnectedTime { get; private set; }
|
|
|
|
/// <summary>
|
|
/// 关闭当前TCP连接
|
|
/// </summary>
|
|
public override void CloseCommunication()
|
|
{
|
|
lock (_connectionLock)
|
|
{
|
|
_isManualDisconnect = true; // 标记为手动断开,不触发自动重连
|
|
|
|
// 停止重连定时器
|
|
if (_reconnectTimer != null)
|
|
{
|
|
_reconnectTimer.Dispose();
|
|
_reconnectTimer = null;
|
|
}
|
|
_isConnecting = false;
|
|
if (Client != null)
|
|
{
|
|
try
|
|
{
|
|
// 取消订阅事件,避免内存泄漏
|
|
Client.PlaintextReceived -= OnPlaintextReceived;
|
|
Client.ServerConnected -= OnServerConnected;
|
|
Client.ServerDisconnected -= OnServerDisconnected;
|
|
|
|
// 关闭连接
|
|
Client.Close();
|
|
Client = null;
|
|
|
|
IsConnected = false;
|
|
LastDisconnectedTime = DateTime.Now;
|
|
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] TCP connection closed manually");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Log($"ERR:FLChargeStation[{SiteId}] failed to close connection: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建TCP连接
|
|
/// </summary>
|
|
public override void CreateCommunication(IPAddress ip, int port)
|
|
{
|
|
lock (_connectionLock)
|
|
{
|
|
// 先关闭旧连接
|
|
if (Client != null)
|
|
{
|
|
CloseCommunication();
|
|
}
|
|
|
|
_endPoint = new IPEndPoint(ip, port);
|
|
_isManualDisconnect = false; // 重置手动断开标记
|
|
_reconnectAttempts = 0; // 重置重连次数
|
|
|
|
ConnectInternal();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 内部连接方法
|
|
/// </summary>
|
|
private void ConnectInternal()
|
|
{
|
|
if (_isConnecting)
|
|
{
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] is already connecting, skip");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_isConnecting = true;
|
|
|
|
// 创建新的TCP客户端
|
|
Client = new AsyncTcpClient(_endPoint.Address, _endPoint.Port);
|
|
Client.PlaintextReceived += OnPlaintextReceived;
|
|
Client.ServerConnected += OnServerConnected;
|
|
Client.ServerDisconnected += OnServerDisconnected;
|
|
|
|
// 连接
|
|
Client.Connect();
|
|
|
|
if (Client.Connected)
|
|
{
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] connecting to {_endPoint.Address}:{_endPoint.Port}...");
|
|
}
|
|
else
|
|
{
|
|
_isConnecting = false;
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] connecting to {_endPoint.Address}:{_endPoint.Port}... 未建立连接");
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Log($"ERR:FLChargeStation[{SiteId}] failed to create connection: {ex.Message}");
|
|
_isConnecting = false;
|
|
|
|
// 触发重连
|
|
if (!_isManualDisconnect)
|
|
{
|
|
ScheduleReconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 手动触发重连
|
|
/// </summary>
|
|
public void Reconnect()
|
|
{
|
|
lock (_connectionLock)
|
|
{
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] manual reconnect triggered");
|
|
|
|
_isManualDisconnect = false;
|
|
_reconnectAttempts = 0;
|
|
|
|
// 关闭现有连接
|
|
if (Client != null)
|
|
{
|
|
try
|
|
{
|
|
Client.PlaintextReceived -= OnPlaintextReceived;
|
|
Client.ServerConnected -= OnServerConnected;
|
|
Client.ServerDisconnected -= OnServerDisconnected;
|
|
Client.Close();
|
|
Client = null;
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// 重新连接
|
|
ConnectInternal();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 安排自动重连
|
|
/// </summary>
|
|
private void ScheduleReconnect()
|
|
{
|
|
if (_isManualDisconnect)
|
|
{
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] manual disconnect, skip auto reconnect");
|
|
return;
|
|
}
|
|
|
|
if (_reconnectAttempts >= MAX_RECONNECT_ATTEMPTS)
|
|
{
|
|
Diagnosis.Log($"ERR:FLChargeStation[{SiteId}] max reconnect attempts ({MAX_RECONNECT_ATTEMPTS}) reached, stop reconnecting");
|
|
return;
|
|
}
|
|
|
|
_reconnectAttempts++;
|
|
|
|
// 停止现有定时器
|
|
if (_reconnectTimer != null)
|
|
{
|
|
_reconnectTimer.Dispose();
|
|
}
|
|
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] scheduling reconnect attempt {_reconnectAttempts}/{MAX_RECONNECT_ATTEMPTS} in {RECONNECT_INTERVAL_MS}ms");
|
|
|
|
// 创建新的定时器
|
|
_reconnectTimer = new Timer(
|
|
callback: _ => AttemptReconnect(),
|
|
state: null,
|
|
dueTime: RECONNECT_INTERVAL_MS,
|
|
period: Timeout.Infinite
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 尝试重连
|
|
/// </summary>
|
|
private void AttemptReconnect()
|
|
{
|
|
lock (_connectionLock)
|
|
{
|
|
if (_isManualDisconnect || IsConnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] attempting to reconnect (attempt {_reconnectAttempts}/{MAX_RECONNECT_ATTEMPTS})...");
|
|
|
|
try
|
|
{
|
|
// 清理旧客户端
|
|
if (Client != null)
|
|
{
|
|
try
|
|
{
|
|
Client.PlaintextReceived -= OnPlaintextReceived;
|
|
Client.ServerConnected -= OnServerConnected;
|
|
Client.ServerDisconnected -= OnServerDisconnected;
|
|
Client.Close();
|
|
}
|
|
catch { }
|
|
Client = null;
|
|
}
|
|
|
|
// 重新连接
|
|
ConnectInternal();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Log($"ERR:FLChargeStation[{SiteId}] reconnect failed: {ex.Message}");
|
|
|
|
// 继续安排下一次重连
|
|
if (!_isManualDisconnect && _reconnectAttempts < MAX_RECONNECT_ATTEMPTS)
|
|
{
|
|
ScheduleReconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnPlaintextReceived(object sender, TcpDatagramReceivedEventArgs<byte[]> e)
|
|
{
|
|
var messageService = CommunicationMessageService.Instance;
|
|
var recBytes = e.Datagram.Take(35).ToArray();
|
|
string IP = ((AsyncTcpClient)sender).RemoteIPEndPoint.Address.ToString();
|
|
Diagnosis.Post($"{string.Join(" ", recBytes.Select(p => $"{p:X2}"))}", $"{IP}", true);
|
|
IsSafe = recBytes[28] == 2;
|
|
messageService.AddReceiveMessage(_endPoint.Address.ToString(), _endPoint.Port, BitConverter.ToString(recBytes).Replace("-", " "), "FRLDTall");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 服务器连接成功事件
|
|
/// </summary>
|
|
private void OnServerConnected(object sender, TcpServerConnectedEventArgs e)
|
|
{
|
|
lock (_connectionLock)
|
|
{
|
|
_isConnecting = false;
|
|
IsConnected = true;
|
|
LastConnectedTime = DateTime.Now;
|
|
_reconnectAttempts = 0; // 重置重连次数
|
|
|
|
Diagnosis.Log($"FLChargeStation[{SiteId}] TCP connected successfully to {_endPoint.Address}:{_endPoint.Port}");
|
|
|
|
// 停止重连定时器
|
|
if (_reconnectTimer != null)
|
|
{
|
|
_reconnectTimer.Dispose();
|
|
_reconnectTimer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 服务器断开连接事件
|
|
/// </summary>
|
|
private void OnServerDisconnected(object sender, TcpServerDisconnectedEventArgs e)
|
|
{
|
|
lock (_connectionLock)
|
|
{
|
|
_isConnecting = false;
|
|
IsConnected = false;
|
|
LastDisconnectedTime = DateTime.Now;
|
|
|
|
Diagnosis.Log($"WARN:FLChargeStation[{SiteId}] TCP disconnected from {_endPoint.Address}:{_endPoint.Port}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 发送充电指令到充电站
|
|
/// </summary>
|
|
public override void SendToChargeStation(int isCharge, Car car,Site site)
|
|
{
|
|
// 检查连接状态
|
|
if (!IsConnected || Client == null)
|
|
{
|
|
Diagnosis.Log($"WARN:FLChargeStation[{SiteId}] not connected, cannot send charge command");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var messageService = CommunicationMessageService.Instance;
|
|
float soc = 0f, voltage = 0, electricCurrent = 0;
|
|
int carId = 0;
|
|
float setVoltage = 55.0f;
|
|
float setElectricCurrent = 50.0f;
|
|
//Site site = null;
|
|
if (car != null)
|
|
{
|
|
soc = float.Parse(Commons.GetCarStatus(car, "Soc"));
|
|
voltage = float.Parse(Commons.GetCarStatus(car, "Voltage"));
|
|
electricCurrent = float.Parse(Commons.GetCarStatus(car, "ElectricCurrent"));
|
|
carId = car.id;
|
|
//site = SimpleLib.GetSite(car.status.holdingLocks.FirstOrDefault());
|
|
|
|
|
|
|
|
}
|
|
if (site != null && site.fields.ContainsKey("setVoltage") && site.fields.ContainsKey("setElectricCurrent"))
|
|
{
|
|
setVoltage = float.Parse(site.fields["setVoltage"]);
|
|
setElectricCurrent = float.Parse(site.fields["setElectricCurrent"]);
|
|
}
|
|
|
|
var msg = GetSendBytes((byte)(isCharge),
|
|
setVoltage, setElectricCurrent, 1000,
|
|
Convert.ToSingle(carId), Convert.ToSingle(soc), Convert.ToSingle(electricCurrent), Convert.ToSingle(voltage));
|
|
|
|
messageService.AddSendMessage(_endPoint.Address.ToString(), _endPoint.Port, BitConverter.ToString(msg).Replace("-", " "), "FRLDTall", site?.name);
|
|
|
|
// 发送数据,带异常处理
|
|
try
|
|
{
|
|
Client.Send(msg);
|
|
Diagnosis.Post($"{string.Join(" ", msg.Select(d => $"{d:X2}"))}",
|
|
$"sendChargeSite: {SiteId}", true);
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
Diagnosis.Log($"ERR:FLChargeStation[{SiteId}] client disposed during send, triggering reconnect");
|
|
IsConnected = false;
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
Diagnosis.Log($"ERR:FLChargeStation[{SiteId}] socket error during send: {ex.SocketErrorCode}, triggering reconnect");
|
|
IsConnected = false;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Diagnosis.Post($"ERR:FLChargeStation[{SiteId}] SwitchCharge Fail: {ExceptionFormatter.FormatEx(e)}");
|
|
}
|
|
}
|
|
|
|
private byte[] GetSendBytes(byte startCharge, float chargeVoltage, float chargeElectricCurrent,
|
|
float chargeTimeSpan, float carId,
|
|
float carSoc, float carElectricCurrent, float carVoltage)
|
|
{
|
|
var sendByte = new byte[32];
|
|
try
|
|
{
|
|
//BB 01 42 48 00 00 42 5C 00 00 00 00 00 02 00 5D 41 B4 00 00 41 28 00 00 00 00 00 00 00 00 00 EE
|
|
sendByte = new byte[2] { 0xBB, startCharge }
|
|
.Concat(BitConverter.GetBytes(chargeElectricCurrent).AsEnumerable().Reverse())
|
|
.Concat(BitConverter.GetBytes(chargeVoltage).AsEnumerable().Reverse())
|
|
.Concat(BitConverter.GetBytes((ushort)chargeTimeSpan).AsEnumerable().Reverse())
|
|
.Concat(BitConverter.GetBytes((ushort)carId).AsEnumerable().Reverse())
|
|
.Concat(BitConverter.GetBytes((ushort)carSoc).AsEnumerable().Reverse())
|
|
.Concat(BitConverter.GetBytes(carElectricCurrent).AsEnumerable().Reverse())
|
|
.Concat(BitConverter.GetBytes(carVoltage).AsEnumerable().Reverse())
|
|
.Concat(new byte[] { 00, 00, 00, 00, 00, 00, 00, 0xEE }).ToArray();
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Post($"下发充电控制{(startCharge == 1 ? "启动" : "停止")}异常+ex:{ExceptionFormatter.FormatEx(ex)}", "error");
|
|
}
|
|
|
|
return sendByte;
|
|
}
|
|
}
|
|
}
|