Files
1257B_DB/JinYuan.Helper/EnergyMeterHelper.cs
2026-08-06 09:23:30 +08:00

444 lines
18 KiB
C#
Raw Permalink 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 JinYuan.Models;
using Org.BouncyCastle.Ocsp;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace JinYuan.Helper
{
public class EnergyMeterHelper
{
public const int BufferSize = 1024;//1kb
private readonly object _lockObj = new object(); // 线程安全锁
private int _timeout = 2000;
private Ping _ping = null;
private IPEndPoint _endPoint = null;
private Socket _energyMeterSocket = null;
private PingReply _pingReply = null;
private string _lastError = "";
public enum ModBusExceptionCode //错误代码
{
IllegalFunction = 01,
IllegalDataAddress,
IllegalDataValue,
SlaveDeviceFailure,
Acknowledge,
SlaveDeviceBusy,
GatewayPathUnavailable,
GatewayTargetDeviceFailed2Respond,
}
/// <summary>
/// 构造函数
/// </summary>
public EnergyMeterHelper()
{
// used to ping the PLC
//
this._ping = new Ping();
// EndPoint parametres
//
this._endPoint = new IPEndPoint(0, 0);
}
/// <summary>
/// set ip and port
/// </summary>
public void SetTCPParams(IPAddress ip, int port)
{
if (ip == null) throw new ArgumentNullException(nameof(ip), "IP地址不能为空");
if (port < 1 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port), "端口号必须在1-65535之间");
// 禁止本地回环地址
if (ip == IPAddress.Loopback || ip == IPAddress.IPv6Loopback)
{
throw new ArgumentException("禁止使用本地回环地址(127.0.0.1),请配置智能电表的真实IP地址", nameof(ip));
}
lock (_lockObj)
{
_endPoint = new IPEndPoint(ip, port); // 重新创建实例,避免修改原有对象
}
}
/// <summary>
/// returns the connection status
/// </summary>
public bool IsConnected
{
get
{
lock (_lockObj)
{
if (_energyMeterSocket == null) return false;
try
{
bool part1 = _energyMeterSocket.Poll(100, SelectMode.SelectRead);
bool part2 = (_energyMeterSocket.Available == 0);
if (part1 && part2)
{
// 连接已断开,释放Socket
Close();
return false;
}
return _energyMeterSocket.Connected;
}
catch (SocketException)
{
Close();
return false;
}
catch (Exception)
{
return false;
}
}
}
}
/// <summary>
/// close the socket
/// </summary>
/// <returns></returns>
public void Close()
{
lock (_lockObj)
{
if (_energyMeterSocket == null) return;
try
{
// 优雅关闭:先关闭发送/接收,再释放
if (_energyMeterSocket.Connected)
{
_energyMeterSocket.Shutdown(SocketShutdown.Both);
}
}
catch { }
finally
{
_energyMeterSocket.Close();
_energyMeterSocket.Dispose();
_energyMeterSocket = null; // 关键:置空,避免后续操作无效套接字
}
}
}
public bool Connect()
{
lock (_lockObj)
{
// 已连接则直接返回
if (IsConnected) return true;
// 先释放旧连接
Close();
try
{
return TCPConnect();
}
catch (Exception ex)
{
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接异常 {ex.Message}");
Close();
return false;
}
}
}
private bool TCPConnect()
{
// 若目标是本地回环地址,直接提示错误(关键!)
if (_endPoint.Address == IPAddress.Loopback || _endPoint.Address == IPAddress.IPv6Loopback)
{
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接地址为本地回环(127.0.0.1),请配置电表真实IP!");
return false;
}
try
{
if (_endPoint.Address == IPAddress.None)
{
throw new InvalidOperationException("未设置有效的IP和端口");
}
// 重新创建Socket实例
_energyMeterSocket = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
{
SendTimeout = _timeout,
ReceiveTimeout = _timeout
};
// 异步连接(避免阻塞,可选)
IAsyncResult result = _energyMeterSocket.BeginConnect(_endPoint, null, null);
bool connectSuccess = result.AsyncWaitHandle.WaitOne(_timeout);
if (connectSuccess && _energyMeterSocket.Connected)
{
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接成功");
return true;
}
// 连接超时,释放当前Socket
_energyMeterSocket.Close();
_energyMeterSocket.Dispose();
_energyMeterSocket = null;
return true;
}
catch (SocketException ex)
{
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:网口连接失败 {ex.SocketErrorCode} - {ex.Message}");
Close();
return false;
}
catch (Exception ex)
{
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:连接失败 {ex.Message}");
Close();
return false;
}
}
public bool Ping()
{
if (_endPoint.Address == IPAddress.None) return false;
try
{
var reply = _ping.Send(_endPoint.Address, _timeout);
return reply.Status == IPStatus.Success;
}
catch (Exception)
{
return false;
}
}
public bool SendAndGetRes(byte[] bytes, ref List<ElectricEnergy> listM)//, ref JetReturnResult jet
{
// 参数校验
if (bytes == null || bytes.Length == 0)
{
_lastError = "发送指令为空";
return false;
}
if (listM == null) listM = new List<ElectricEnergy>();
var arrayPool = ArrayPool<byte>.Shared;
byte[] buffer = arrayPool.Rent(BufferSize);//new byte[1024];
byte[] recvData = new byte[88 * 4 + 9];//0x58
int size = recvData.Length; //0x58
try
{
if (!Ping())
{
_lastError = "Ping设备失败,网络不可达";
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:{_lastError}");
return false;
}
if (!IsConnected && !Connect())
{
_lastError = "重连设备失败";
return false;
}
// 发送
Send(bytes);
// 接收
int recvLen = Receive(ref recvData, size);
if (recvLen <= 0)
{
_lastError = "未接收到设备响应";
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now:yyyy-MM-dd}.txt",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 智能电表:{_lastError}");
return false;
}
// 解析
if ((recvData[0] == Convert.ToByte(01 >> 8) && recvData[1] == Convert.ToByte(01)))//pTask.id
{
if (recvData[7] == 0x04)//读线圈寄存器功能码
{
//数据赋值
Array.Copy(recvData, 0, buffer, 0, recvData.Length);
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取原始数据:{string.Join(" ", recvData)}");
decimal PhaseVoltageA = Convert.ToDecimal(HextoFloat(buffer[9], buffer[10], buffer[11], buffer[12]));
decimal PhaseVoltageB = Convert.ToDecimal(HextoFloat(buffer[13], buffer[14], buffer[15], buffer[16]));
decimal PhaseVoltageC = Convert.ToDecimal(HextoFloat(buffer[17], buffer[18], buffer[19], buffer[20]));
decimal PhaseCurrentA = Convert.ToDecimal(HextoFloat(buffer[21], buffer[22], buffer[23], buffer[24]));
decimal PhaseCurrentB = Convert.ToDecimal(HextoFloat(buffer[25], buffer[26], buffer[27], buffer[28]));
decimal PhaseCurrentC = Convert.ToDecimal(HextoFloat(buffer[29], buffer[30], buffer[31], buffer[32]));
decimal PhasePowerA = Convert.ToDecimal(HextoFloat(buffer[33], buffer[34], buffer[35], buffer[36]));
decimal PhasePowerB = Convert.ToDecimal(HextoFloat(buffer[37], buffer[38], buffer[39], buffer[40]));
decimal PhasePowerC = Convert.ToDecimal(HextoFloat(buffer[41], buffer[42], buffer[43], buffer[44]));
decimal ActivePower = Convert.ToDecimal(HextoFloat(buffer[121], buffer[122], buffer[123], buffer[124]));
decimal PowerFactor = Convert.ToDecimal(HextoFloat(buffer[133], buffer[134], buffer[135], buffer[136]));
decimal pt = 1;
decimal ct = 150 / 5;
decimal AccumulatedElectricity = Convert.ToDecimal(HextoFloat(buffer[181], buffer[182], buffer[183], buffer[184]));
ElectricEnergy m = new ElectricEnergy();
m.PhaseVoltageA = PhaseVoltageA;
m.PhaseVoltageB = PhaseVoltageB;
m.PhaseVoltageC = PhaseVoltageC;
m.PhaseCurrentA = PhaseCurrentA * ct;
m.PhaseCurrentB = PhaseCurrentB * ct;
m.PhaseCurrentC = PhaseCurrentC * ct;
m.PhasePowerA = PhaseVoltageA * PhaseCurrentA * ct;
m.PhasePowerB = PhaseVoltageB * PhaseCurrentB * ct;
m.PhasePowerC = PhaseVoltageC * PhaseCurrentC * ct;
m.ActivePower = ActivePower * ct;
m.PowerFactor = PowerFactor * ct;
m.pt = pt;//Convert.ToDecimal(HextoFloat(buffer[0], buffer[0], buffer[0], buffer[0]));
m.ct = ct;//Convert.ToDecimal(HextoFloat(buffer[0], buffer[0], buffer[0], buffer[0]));
m.AccumulatedElectricity = AccumulatedElectricity;
listM.Add(m);
}
else
{
switch (recvData[8])
{
case (byte)ModBusExceptionCode.IllegalDataAddress:
_lastError = string.Format("SendAndGetRes():ReadInputRegister [IllegalDataAddress]");
break;
case (byte)ModBusExceptionCode.IllegalDataValue:
_lastError = string.Format("SendAndGetRes():ReadInputRegister [IllegalDataValue]");
break;
case (byte)ModBusExceptionCode.IllegalFunction:
_lastError = string.Format("SendAndGetRes():ReadInputRegister [IllegalFunction]");
break;
default:
_lastError = string.Format("SendAndGetRes():ReadInputRegister [FunctionException]");
break;
}
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取数据异常,原因:{_lastError}");
}
}
}
catch (Exception ex)
{
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取原始数据:{string.Join(" ", recvData)}");
TxtHelper.WriteTxt($@"D:\APILog\Logs\智能电表信息\{DateTime.Now.ToString("yyyy-MM-dd")}.txt", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + $"电能表读取数据异常,原因:{ex}");
Close();
return false;
}
finally
{
arrayPool.Return(buffer);
}
return true;
}
/// <summary>
/// send a command to energy meter device
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
private int Send(Byte[] command)
{
lock (_lockObj)
{
// 核心校验:Socket不为null且连接有效
if (_energyMeterSocket == null || !IsConnected)
{
throw new SocketException((int)SocketError.NotConnected);
}
int bytesSent = _energyMeterSocket.Send(command, 0, command.Length, SocketFlags.None);
return bytesSent;
}
}
/// <summary>
/// receives a response from the plc
/// </summary>
/// <param name="response"></param>
/// <param name="respLen"></param>
/// <returns></returns>
private int Receive(ref Byte[] response, int expectLen)
{
lock (_lockObj)
{
if (_energyMeterSocket == null || !IsConnected)
{
throw new SocketException((int)SocketError.NotConnected);
}
int totalRecv = 0;
while (totalRecv < expectLen)
{
// 分段接收,避免单次接收不足
int recvLen = _energyMeterSocket.Receive(response, totalRecv, expectLen - totalRecv, SocketFlags.None);
if (recvLen == 0)
{
throw new SocketException((int)SocketError.ConnectionReset);
}
totalRecv += recvLen;
}
return totalRecv;
}
}
/// <summary>
/// HexToFloat
/// </summary>
/// <param name="H1">高字高字节</param>
/// <param name="H2">高字低字节</param>
/// <param name="D1">低字高字节</param>
/// <param name="D2">低字低字节</param>
/// <returns>float</returns>
public static float HextoFloat(byte H1, byte H2, byte D1, byte D2)
{
try
{
//byte-->short
int s1, s2;
s1 = Convert.ToInt32(H1 * 256) + Convert.ToInt32(H2);
s2 = Convert.ToInt32(D1 * 256) + Convert.ToInt32(D2);
//将输入数值short转化为无符号unsigned short
int us1 = s1, us2 = s2;
if (s1 < 0) us1 += 65536;
if (s2 < 0) us2 += 65536;
//sign: 符号位, exponent: 阶码, mantissa:尾数
int sign, exponent;
float mantissa;
//计算符号位
sign = us1 / 32768;
//去掉符号位
int emCode = us1 % 32768;
//计算阶码
exponent = emCode / 128;
//计算尾数
mantissa = (float)(emCode % 128 * 65536 + us2) / 8388608;
//代入公式 fValue = (-1) ^ S x 2 ^ (E - 127) x (1 + M)
return (float)Math.Pow(-1, sign) * (float)Math.Pow(2, exponent - 127) * (1 + mantissa);
}
catch (Exception ex)
{
return 0;
}
}
}
}