添加项目文件。
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user