添加项目文件。

This commit is contained in:
xyy
2026-06-13 14:16:34 +08:00
parent 9df508aa36
commit 3fb35f5814
23 changed files with 1251 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
using AciTester.Models;
using OfficeOpenXml;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
namespace AciTester.Services
{
public class ExcelReportService : IReportService
{
public async Task GenerateReportAsync(TestResult result, string filePath)
{
ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
using var package = new ExcelPackage();
var sheet = package.Workbook.Worksheets.Add("ACI测试报告");
// 标题
sheet.Cells["A1"].Value = "中国药典2025版四部通则0951装置3测试报告";
sheet.Cells["A1:C1"].Merge = true;
sheet.Cells["A1"].Style.Font.Bold = true;
sheet.Cells["A1"].Style.Font.Size = 16;
// 基本信息
sheet.Cells["A3"].Value = "测试时间:";
sheet.Cells["B3"].Value = result.TestTime.ToString("yyyy-MM-dd HH:mm:ss");
sheet.Cells["A4"].Value = "采样流量(L/min):";
sheet.Cells["B4"].Value = result.FlowRate;
sheet.Cells["A5"].Value = "总质量F(g):";
sheet.Cells["B5"].Value = result.TotalMass;
// 在报告生成时添加
sheet.Cells["A6"].Value = "测试环境:";
sheet.Cells["B6"].Value = $"流量: {result.FlowRate:F2} L/min, 温度: {result.Temperature:F1}℃, 压差: {result.DifferentialPressure:F2} kPa";
// 微细粒子指标
sheet.Cells["A7"].Value = "微细粒子剂量(FPD)";
sheet.Cells["B7"].Value = $"{result.FineParticleDose:F2} mg";
sheet.Cells["A8"].Value = "微细粒子分数(FPF)";
sheet.Cells["B8"].Value = $"{result.FineParticleFraction:F2}%";
// 各级数据表
sheet.Cells["A10"].Value = "层级";
sheet.Cells["B10"].Value = "截止直径(μm)";
sheet.Cells["C10"].Value = "净重(g)";
sheet.Cells["D10"].Value = "占比(%)";
sheet.Cells["A10:D10"].Style.Font.Bold = true;
int row = 11;
double total = result.TotalMass;
foreach (var stage in result.Stages)
{
double percent = total > 0 ? stage.NetWeight / total * 100 : 0;
sheet.Cells[row, 1].Value = stage.StageName;
sheet.Cells[row, 2].Value = stage.CutoffDiameter > 0 ? stage.CutoffDiameter.ToString("F1") : "—";
sheet.Cells[row, 3].Value = stage.NetWeight;
sheet.Cells[row, 4].Value = percent.ToString("F2");
row++;
}
sheet.Cells.AutoFitColumns();
await package.SaveAsAsync(new FileInfo(filePath));
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using AciTester.Models;
namespace AciTester.Services
{
public interface IReportService
{
Task GenerateReportAsync(TestResult result, string filePath);
}
}

View File

@@ -0,0 +1,207 @@
using Modbus.Device;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using AciTester.Models;
namespace AciTester.Services
{
public class ModbusTcpPlcService : IPlcService, IDisposable
{
private readonly PlcConfiguration _config;
private TcpClient _tcpClient;
private IModbusMaster _master;
public ModbusTcpPlcService(PlcConfiguration config)
{
_config = config;
}
public async Task EnsureConnectedAsync(int retryCount = 3)
{
if (_tcpClient != null && _tcpClient.Connected)
return;
for (int i = 0; i < retryCount; i++)
{
try
{
_tcpClient?.Close();
_tcpClient = new TcpClient();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
// 现在可以直接使用扩展方法
await _tcpClient.ConnectAsync(_config.IpAddress, _config.Port).WithCancellation(cts.Token);
_master = ModbusIpMaster.CreateIp(_tcpClient);
return;
}
catch (Exception ex) when (i < retryCount - 1)
{
System.Diagnostics.Debug.WriteLine($"连接失败,{500}ms 后重试... {ex.Message}");
await Task.Delay(500);
}
}
throw new Exception($"无法连接到 PLC ({_config.IpAddress}:{_config.Port}),请检查网络和 PLC 状态。");
}
// 读取两个连续的保持寄存器转换为32位浮点数假设大端模式
public async Task<float> ReadFloatAsync(ushort startAddress)
{
await EnsureConnectedAsync();
var registers = await ReadHoldingRegistersAsync(startAddress, 2);
return UshortToFloat(registers[1], registers[0]);
}
public async Task<float> ReadPressureAsync() =>
await ReadFloatAsync(_config.PressureRegister);
public async Task<float> ReadWetFlowAsync(int stationId)
{
ushort startAddress = stationId switch
{
1 => _config.WetFlowRegister,
2 => _config.WetFlowRegister2,
3 => _config.WetFlowRegister3,
};
return await ReadFloatAsync(startAddress);
}
public async Task WriteCoilAsync(ushort coilAddress, bool value)
{
await EnsureConnectedAsync();
await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value);
}
public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
{
await EnsureConnectedAsync();
await Task.Delay(100);
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
}
public async Task<bool> ReadCoilAsync(ushort coilAddress)
{
await EnsureConnectedAsync();
await Task.Delay(100);
bool[] result = await _master?.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
return result[0];
}
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
{
await EnsureConnectedAsync();
// await Task.Delay(100);
return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count);
}
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
{
await EnsureConnectedAsync();
int val = (int)value;
await Task.Delay(100);
await _master.WriteMultipleRegistersAsync(1, registerAddress, intToushorts(val));
}
public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
{
await EnsureConnectedAsync();
await Task.Delay(100);
await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray((float)value));
}
/// <summary>
/// Int转为ushort数组发送
/// </summary>
/// <param name="res"></param>
/// <returns>返回ushort数组</returns>
private ushort[] intToushorts(int res)
{
ushort ust1 = (ushort)(res >> 16);
ushort ust2 = (ushort)res;
return new ushort[] { ust2, ust1 };
}
/// <summary>
/// Float转为Ushort数组发送
/// </summary>
/// <param name="value"></param>
/// <returns>返回ushort数组</returns>
public ushort[] SplitFloatToUShortArray(float value)
{
byte[] floatBytes = BitConverter.GetBytes(value);
ushort[] ushortArray = new ushort[floatBytes.Length / 2];
for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
{
ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
}
return ushortArray;
}
/// <summary>
/// ushort转为float类型
/// </summary>
/// <param name="P1"></param>
/// <param name="P2"></param>
/// <returns>float型数据</returns>
public float UshortToFloat(ushort P1, ushort P2)
{
int intSign, intSignRest, intExponent, intExponentRest;
float faResult, faDigit;
intSign = P1 / 32768;
intSignRest = P1 % 32768;
intExponent = intSignRest / 128;
intExponentRest = intSignRest % 128;
faDigit = (float)(intExponentRest * 65536 + P2) / 8388608;
faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
return faResult;
}
// 新增读取压力(根据工位)
public async Task<float> ReadPressureAsync(int stationId)
{
ushort startAddress = stationId switch
{
1 => _config.PressureRegisterStation1,
2 => _config.PressureRegisterStation2,
3 => _config.PressureRegisterStation3,
_ => throw new ArgumentException("Invalid station")
};
return await ReadFloatAsync(startAddress);
}
public void Dispose()
{
_master?.Dispose();
_tcpClient?.Close();
_tcpClient?.Dispose();
}
}
public static class TaskExtensions
{
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
await task;
}
}
}