QianTools 2.7.1

dotnet add package QianTools --version 2.7.1
                    
NuGet\Install-Package QianTools -Version 2.7.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="QianTools" Version="2.7.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="QianTools" Version="2.7.1" />
                    
Directory.Packages.props
<PackageReference Include="QianTools" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add QianTools --version 2.7.1
                    
#r "nuget: QianTools, 2.7.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package QianTools@2.7.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=QianTools&version=2.7.1
                    
Install as a Cake Addin
#tool nuget:?package=QianTools&version=2.7.1
                    
Install as a Cake Tool

QianTools

.NET 工业自动化与系统管理工具库,支持 net6.0 / net8.0。

作者:钱军涛 | 公司:浪涛科技有限公司 | 许可证:MIT

安装

dotnet add package QianTools

项目结构

QianTools/
├── Core/                    # 基础设施(日志、配置)
│   ├── LogManager.cs        # 日志管理器(支持 ILogger 接口、级别过滤、文件滚动)
│   ├── ILogger.cs           # 日志接口
│   ├── LogLevel.cs          # 日志级别枚举
│   ├── ConfigManager.cs     # 配置管理器(JSON/XML,策略模式可扩展)
│   ├── IConfigParser.cs     # 配置解析器接口
│   ├── JsonParser.cs        # JSON 解析器
│   └── XmlParser.cs         # XML 解析器
├── Helpers/                 # 通用工具类
│   ├── AllTools.cs          # 统一外观入口(FileTools / CryptoTools / NetworkTools)
│   ├── CryptoHelper.cs      # 加密与哈希(SHA256、MD5)
│   ├── FileHelper.cs        # 文件/目录复制与删除
│   └── NetworkHelper.cs     # HTTP 文件下载
├── System/                  # 系统硬件信息
│   ├── SystemHardwareInfo.cs    # 串口、磁盘、CPU、操作系统、网络适配器
│   └── MachineCodeGenerator.cs # 机器码生成与授权验证
├── Plc/                     # PLC 通信
│   ├── MelsecPlcService.cs  # 三菱 FX 底层通信服务(ASCII 协议)
│   └── MelsecPlcHelper.cs   # 上层静态封装
├── EtherNetIP/              # EtherNet/IP 与 Modbus TCP 通信
│   ├── EtherNetIPClient.cs  # EtherNet/IP 显式消息客户端
│   ├── Encapsulation.cs     # 封装协议头操作
│   ├── CipMessage.cs        # CIP 协议消息构建与解析
│   ├── EdsAssemblyReader.cs # Assembly 数据读取器
│   └── Ex1121ModbusClient.cs# EX-1121 Modbus TCP IO 客户端
├── Ezd/                     # JCZ激光打标板卡控制
│   ├── EzdClient.cs         # 高层封装(单例),生命周期/异步/业务级 API
│   ├── EzdNative.cs         # P/Invoke 底层接口声明
│   └── EzdErrorCode.cs      # 故障码定义与中文消息
├── Application/             # 应用层服务
│   ├── AutoStartManager.cs  # 注册表开机自启动
│   ├── ShortcutCreator.cs   # 桌面快捷方式
│   ├── VerifyInstallation.cs# 软件安装与验证
│   ├── MesClient.cs         # MES HTTP 客户端
│   └── MesTcpClient.cs     # MES TCP 通信客户端(JSON 换行协议)
├── QianTools.csproj
└── README.md

功能模块

Core — 基础设施

日志管理 — LogManager
// 使用静态方法(默认单例)
LogManager.Info("设备启动成功");
LogManager.Error($"通信失败: {ex.Message}");

// 自定义日志路径和文件滚动
LogManager.Initialize(@"D:\AppLogs\myapp.log");
LogManager.MinLogLevel = LogLevel.Debug;
LogManager.MaxFileSizeBytes = 10 * 1024 * 1024;  // 10MB 自动滚动

// 通过 ILogger 接口注入(支持单元测试)
ILogger logger = LogManager.Instance;
logger.LogInfo("消息");
配置管理 — ConfigManager

支持 JSON(默认)和 XML 两种格式,策略模式可扩展。

// ===== JSON 模式(默认) =====
var cfg = new ConfigManager();                          // 或 ConfigManager.Default
cfg.Set("AppSettings", new AppSettings { Theme = "Dark" });
var settings = cfg.Get<AppSettings>("AppSettings");     // 从内存读取
cfg.Save();                                              // 持久化到文件
cfg.Load();                                              // 从文件加载到内存
cfg.Remove("AppSettings");

// ===== XML 模式 =====
var xmlCfg = new ConfigManager(useXml: true, customPath: @"D:\MyApp\Config");
xmlCfg.Set("AppSettings", new AppSettings { Theme = "Dark" });
xmlCfg.Save();  // → D:\MyApp\Config\config.xml

// ===== 错误监听 =====
ConfigManager.OnError += msg => Console.WriteLine(msg);

Helpers — 通用工具

统一入口 — AllTools
// 通过 AllTools 静态子类访问
AllTools.FileTools.CopyDirectory(@"C:\Source", @"D:\Backup", overwrite: true);
AllTools.FileTools.DeleteDirectory(@"C:\Temp\OldData");
string sha256 = AllTools.CryptoTools.ComputeSha256Hash("hello");
string md5 = AllTools.CryptoTools.ComputeMd5Hash("hello");
await AllTools.NetworkTools.DownloadFileAsync(url, destinationPath);

// Excel 操作
AllTools.ExcelTools.WriteToExcel(dataList, @"D:\data.xlsx", "Sheet1");
DataTable dt = AllTools.ExcelTools.ReadToDataTable(@"D:\data.xlsx");
List<MyClass> list = AllTools.ExcelTools.ReadToList<MyClass>(@"D:\data.xlsx");
文件操作 / 加密 / 网络 / Excel
// 文件
FileHelper.CopyDirectory(@"C:\Source", @"D:\Backup", overwrite: true);
FileHelper.DeleteDirectory(@"C:\Temp\OldData");

// 加密
string sha256 = CryptoHelper.ComputeSha256Hash("hello");
string md5 = CryptoHelper.ComputeMd5Hash("hello");

// 网络
await NetworkHelper.DownloadFileAsync("https://example.com/file.zip", @"D:\Download\file.zip");

// Excel(基于 ClosedXML,仅 .xlsx)
ExcelHelper.WriteToExcel(dataList, @"D:\data.xlsx", "Sheet1");
ExcelHelper.WriteDataTable(dataTable, @"D:\data.xlsx", "Sheet1");
DataTable dt = ExcelHelper.ReadToDataTable(@"D:\data.xlsx");
List<MyClass> list = ExcelHelper.ReadToList<MyClass>(@"D:\data.xlsx");
string[] sheets = ExcelHelper.GetSheetNames(@"D:\data.xlsx");

System — 系统信息

硬件信息 — SystemHardwareInfo
string ports = SystemHardwareInfo.GetPort();          // 串口列表
string disks = SystemHardwareInfo.GetDiskInfo();       // 磁盘信息
string cpu = SystemHardwareInfo.GetCpuInfo();          // CPU 信息
string os = SystemHardwareInfo.GetOsInfo();            // 操作系统
string network = SystemHardwareInfo.GetNetworkAdapterInfo(); // 网络适配器
机器码与授权 — MachineCodeGenerator
// 生成 24 位机器码(CPU + 硬盘 + 网卡)
string machineCode = MachineCodeGenerator.GenerateMachineCode();
string hash = MachineCodeGenerator.GetMachineCodeHash();  // SHA256 大写

// ── 无口令模式 ──
// 授权方生成注册码
string licenseKey = MachineCodeGenerator.GenerateLicenseKey(
    machineCode, new DateTime(2026, 12, 31));

// 用户端验证(返回 LicenseResult,可解构为 tuple)
var (isValid, expiryDate) = MachineCodeGenerator.ValidateLicenseKey(licenseKey, machineCode);

// ── 口令保护模式 ──
string key = MachineCodeGenerator.GenerateLicenseKey(
    machineCode, new DateTime(2026, 12, 31), password: "admin@123");

var (ok, expiry) = MachineCodeGenerator.ValidateLicenseKey(key, machineCode, "admin@123");

// ── 详细诊断 ──
LicenseResult result = MachineCodeGenerator.ValidateLicenseKey(key, machineCode);
// result.IsValid / result.ExpiryDate / result.ErrorReason
// result.MachineCodeOk / result.DateOk / result.PasswordOk / result.HasPassword

Plc — PLC 通信

MelsecPlcHelper

支持三菱 FX 系列(X/Y/M/S/T/C/D 元件),ASCII 协议串口通信。

// 连接 PLC
MelsecPlcHelper.Connect("COM1", baudRate: 9600);

// 读取
bool x0 = MelsecPlcHelper.ReadX(0);
bool y1 = MelsecPlcHelper.ReadY(1);
bool m10 = MelsecPlcHelper.ReadM(10);
short d100 = MelsecPlcHelper.ReadD(100);
short t5 = MelsecPlcHelper.ReadT(5);

// 写入
MelsecPlcHelper.WriteY(1, true);
MelsecPlcHelper.WriteM(10, false);
MelsecPlcHelper.WriteD(100, 12345);

// 轮询 D 寄存器
MelsecPlcHelper.OnDataUpdated += snapshot => {
    Console.WriteLine($"D100 = {snapshot.D[100]}");
};
MelsecPlcHelper.StartPolling(new[] { 100, 101, 102 }, intervalMs: 100);

// 日志监听
MelsecPlcHelper.OnLog += (msg, level) => Console.WriteLine($"[{level}] {msg}");

// 断开
MelsecPlcHelper.Shutdown();

EtherNetIP — EtherNet/IP 与 Modbus TCP 通信

命名空间: QianTools.EtherNetIP

支持 EX-1121(DECOWELL/Keyence 兼容)远程 IO 模块,提供 EtherNet/IP 显式消息和 Modbus TCP 两种通信方式。

EtherNet/IP 客户端
using QianTools.EtherNetIP;

var client = new EtherNetIPClient("192.168.1.100", timeout: TimeSpan.FromSeconds(3));
await client.ConnectAsync();

// 读取输入 Assembly(Instance 100)— 过程镜像输入数据
byte[] inputData = await client.ReadInputAssemblyAsync();
var reader = new EdsAssemblyReader(inputData);

// 按偏移量读取数据
ushort ch0 = reader.ReadUInt16(0);        // 16 位无符号
short ch1 = reader.ReadInt16(2);          // 16 位有符号
uint ch2 = reader.ReadUInt32(4);          // 32 位无符号
bool bit0 = reader.ReadBool(0, 0);        // 位读取

// 读取输出 Assembly(Instance 101)
byte[] outputData = await client.ReadOutputAssemblyAsync();

// 显式消息:GetAttributeSingle
byte[] data = await client.GetAttributeAsync(
    classId: 0x04, instanceId: 100, attributeId: 0x03);

// 显式消息:SetAttributeSingle
await client.SetAttributeAsync(0x04, 101, 0x03, new byte[] { 0x00, 0x01 });

// Keyence / Decowell EDS 参数读取(服务 0x4C)
ushort param = await client.ReadParameterUInt16Async(slot: 1, paramNo: 512);
short param2 = await client.ReadParameterInt16Async(slot: 1, paramNo: 513);
uint param3 = await client.ReadParameterUInt32Async(slot: 1, paramNo: 514);

client.Disconnect();
Modbus TCP 客户端
var config = Ex1121Config.Default;  // DI=0xA000, DO=0xB000, 可自定义
using var io = new Ex1121ModbusClient("169.254.193.30", config, port: 20001);
await io.ConnectAsync();

// 读 DI
ushort[] inputs = await io.ReadInputsAsync();
bool di15 = Ex1121ModbusClient.GetDiBit(inputs, 15);

// 读 DO
ushort[] outputs = await io.ReadOutputsAsync();
bool do0 = Ex1121ModbusClient.GetDoBit(outputs, 0);

// 写 DO
await io.WriteOutputsAsync(0x0001);              // DO0 = ON
await io.SetDoBitAsync(3, true);                 // DO3 = ON

// 自定义寄存器地址读写
ushort[] regs = await io.ReadRegistersAsync(0xA000, 2);
await io.WriteRegistersAsync(0xB000, new ushort[] { 0x0001 });

io.Disconnect();

Application — 应用层服务

MES 通信 — MesClient
// GET 请求
string result = await MesClient.SendGetRequestAsync(
    "https://mes-server/api", "production/orders", apiKey: "your-token");

// POST 请求
var data = new { OrderId = "P001", Quantity = 100 };
string response = await MesClient.SendPostRequestAsync(
    "https://mes-server/api", "production/report", data, apiKey: "your-token");
MES TCP 通信 — MesTcpClient

基于 TCP + 换行分隔 JSON 协议,支持请求-响应模式和服务器主动推送。线程安全,适合与产线 MES 服务器进行长连接双向通信。

using QianTools.Mes;

var mes = MesTcpClient.Instance;

// ── 超时设置(连接前或连接后均可设置)──
mes.TimeoutMs = 10000;                                    // 请求-响应超时 10 秒

// ── 连接 ──
bool ok = await mes.ConnectAsync("192.168.1.100", 8080);  // 使用 TimeoutMs 属性值
// 也可在连接时显式传入超时
bool ok2 = await mes.ConnectAsync("192.168.1.100", 8080, timeoutMs: 3000);
// 连接状态通过事件通知
mes.OnConnectionChanged += connected => Console.WriteLine(connected ? "已连接" : "已断开");
mes.OnMessageReceived += json => Console.WriteLine($"服务器推送: {json}");
mes.OnError += err => Console.WriteLine($"异常: {err}");

// ── 测试连接(发送 ping)──
var (pingOk, pingResp) = await mes.TestAsync();

// ── 上报打标结果 ──
var (resultOk, resultResp) = await mes.SendResultAsync(
    productCode: "PC001",
    workOrder: "WO-20260523",
    productionLine: "L1",
    success: true,
    entityName: "Text1",
    fileName: @"C:\Files\product.ezd");

// ── 通用请求-响应 ──
string? response = await mes.SendAndReceiveAsync("{\"cmd\":\"query_status\"}");

// ── 单向发送(不等待响应)──
await mes.SendAsync("{\"cmd\":\"heartbeat\"}");

// ── 断开 ──
mes.Disconnect();

协议格式:每条 JSON 消息以 \n 结尾。常见消息类型:

方向 cmd 说明
客户端→服务端 ping 心跳/连通性测试
客户端→服务端 mark_result 上报打标结果(含产品代码、工单号、成功/失败等)
服务端→客户端 start_mark 要求开始标刻
服务端→客户端 select_entity 切换打标对象
服务端→客户端 update_product 更新生产信息(产品代码/工单号等)

线程安全:所有 I/O 通过 SemaphoreSlim 串行化。后台读循环自动处理服务器主动推送的消息。

桌面快捷方式 — ShortcutCreator
ShortcutCreator.CreateShortcut("我的应用", @"D:\App\MyApp.exe", arguments: "-start");
开机启动 — AutoStartManager
AutoStartManager.EnsureAutoStart("MyApp", @"D:\App\MyApp.exe", enable: true);
AutoStartManager.EnsureAutoStart("MyApp", null, enable: false);  // 取消
安装验证 — VerifyInstallation
// 检查软件是否安装
bool installed = VerifyInstallation.IsSoftwareInstalled("MyApp", version: "2.0");

// 安装程序(需用户确认委托)
VerifyInstallation.Notify = (title, msg) => MessageBox.Show(msg, title) == DialogResult.OK;
await VerifyInstallation.InstallAsync("安装 MyApp", @"D:\Installer", "setup.exe");

Ezd — JCZ 激光打标板卡控制

命名空间: QianTools.Ezd
平台要求: MarkEzd.dll 为 32 位原生库,宿主程序必须编译为 x86(<PlatformTarget>x86</PlatformTarget>)。x64 进程调用会直接返回 EzdErrorCode.ERR_PLATFORM_X86_REQUIRED (2001),不会崩溃。
运行依赖: 初始化时传入的 EZCAD 目录需包含 MarkEzd.dll、EZCAD.CFG、plug/、PARAM/、FONT/ 等 EZCAD 运行时文件;运行前必须关闭 ezcad2.exe(否则初始化返回 1“EZCAD 正在运行”)。

通过 P/Invoke 封装 MarkEzd.dll(对照官方《EZCAD2 开发者手册》v3.1,__cdecl + Unicode),提供初始化/关闭、文件加载保存、实体管理、标刻/红光、笔号/填充/字体/条码/位图参数、IO/锁存、振镜/扩展轴、连续飞行缓冲区等功能。

using QianTools.Ezd;

var ezd = EzdClient.Instance;

// ── 初始化(失败不会把状态卡在 Initializing;重复初始化会被拒绝)──
int ret = ezd.Initialize2(@"C:\Laser\EzCad2", testMode: false);
if (!EzdErrorCode.IsSuccess(ret))
{
    Console.WriteLine($"初始化失败: {EzdErrorCode.GetMessage(ret)}");
    return;
}

// ── 文件操作 ──
ezd.LoadEzdFile(@"C:\Files\product.ezd");
ezd.SaveEzdFile(@"C:\Files\output.ezd");

// ── 实体管理 ──
int count = ezd.GetEntityCount();
string firstName = ezd.GetEntityName(0);
// AddText(text, name, x, y, z, align, angle(弧度), penNo, hatch)
ezd.AddText("Hello", "Text1", 0, 0, 0, 1, 0, 1, false);
// AddCircle(cx, cy, radius, name, penNo) —— 手册无 hatch 参数
ezd.AddCircle(0, 0, 10, "Circle1", 1);
ezd.MoveEntity("Text1", 10, 5);
ezd.RotateEntity("Text1", 0, 0, Math.PI / 4);   // 角度单位:弧度
ezd.SetText("Text1", "NewText");                // 修改文本内容
ezd.DeleteEntity("Text1");

// ── 标刻控制(异步:阻塞的原生函数已用 Task.Run 包装)──
int r1 = await ezd.MarkAsync(flyMark: false);
int r2 = await ezd.MarkEntityAsync("Text1");
int r3 = await ezd.MarkLineAsync(0, 0, 50, 50, 1);
int r4 = await ezd.MarkPointAsync(100, 100, 1, 1);
ezd.StopMark();

// ── 红光预览 ──
await ezd.RedLightMarkAsync();
await ezd.RedLightContourAsync();
await ezd.RedLightEntityAsync("Text1", contour: true);
// 连续红光:在调用线程内循环,需自行 Task.Run 且提供停止条件
Task.Run(() => ezd.RedLightContourLoop(() => shouldStop));

// ── 预览图(HBITMAP 必须手动释放;Cbitmap* 版本不要用 DeleteObject)──
IntPtr hBmp = ezd.GetPreviewBitmap(320, 240);
IntPtr hBmp2 = ezd.GetEntityPreviewBitmap("Text1", 320, 240);
// 需要自定义视口时:GetPreviewBitmap3(width, height, originX, originY, scale)
if (hBmp != IntPtr.Zero) EzdNative.DeleteObject(hBmp);
if (hBmp2 != IntPtr.Zero) EzdNative.DeleteObject(hBmp2);

// ── 组合 / 复制 / 填充(source=填充参数来源对象,target=被填充对象)──
ezd.GroupEntities("A", "B", "AB", 1);
ezd.UnGroupEntity("AB");
ezd.CopyEntity("A", "A_copy");
ezd.SetDefaultHatchParam(enableContour: true, paramIndex: 1, enableHatch: true,
    penNo: 1, hatchType: 0, allCalc: false, edge: false, averageLine: false,
    Math.PI / 4, 0.05, 0, 0, 0, 0, 0, 1, false, false, 0);
ezd.HatchEntity("Source", "Target");

// ── 笔号参数(完整 32 参数版本)──
ezd.SetPenParam4(1, "MyPen", 0xFF0000, false, false, 1, 1000, 50, 10,
    20000, 1, 0, 0, 0, 0, 5000, 0, 0, 0, 0, false, 0, 0, false, 0,
    false, 0, 0, 0, false, 0, 0);

// ── IO / 锁存 / 振镜 / 扩展轴 ──
ezd.WritePort(0x00FF);
ezd.ReadPort(out ushort inData);
ezd.LaserOn(false);
ezd.EnableLock(true);                  // 参数 bLowToHigh:true=启用锁存
ezd.ClearLock();
ezd.GotoPos(100, 100);
ezd.GetCurCoor(out double gx, out double gy);
ezd.InitAxis(true, 0.5, 1, 50, false, 0, 0, 0);   // 先 Reset 使能,再写入轴参数
ezd.AxisMoveTo(0, 120);

// ── 条码(barWidthScale / spaceWidthScale 对应手册 double[4],长度必须 ≥4)──
ezd.AddBarcode("123456", "BC1", 0, 0, 0, 1, 1, false, 0, 0, 10, 0.3,
    new double[4], new double[4], 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial");

// ── 位图参数(按手册 8 参数版本,返回位图路径)──
ezd.GetBitmapParam("BC1", out string bmpPath, out int bmpAttrib, out int scanAttrib,
    out double brightness, out double contrast, out double pointTime, out int dpi);
// 完整版:GetBitmapParam2 / SetBitmapParam2(含低灰度值)
// DPI + 256 级灰度映射:GetBitmapParam3 / SetBitmapParam3

// ── 连续飞行缓冲区(SetTextName 最多 6 个;FlyAdd = count + 最多 6 个数据)──
ezd.ContinueBufferSetTextName("T1", "T2");
ezd.ContinueBufferFlyAdd(2, "A", "B");
int flyStart = await ezd.ContinueBufferFlyStartAsync();

// ── 错误处理 / 事件 ──
ezd.OnError += msg => Console.WriteLine($"EZCAD 错误: {msg}");
if (!EzdErrorCode.IsSuccess(flyStart))
    Console.WriteLine(EzdErrorCode.GetMessage(flyStart));

// ── 退出:关闭板卡并卸载 MarkEzd.dll ──
ezd.Shutdown();

说明:2.7.1 起按官方手册修正了 lmc1_GetPrevBitmap3、4 个位图参数函数、lmc1_AddBarCodeToLib、lmc1_AddCircleTextToLib、连续飞行、EnableLockInputPort、笔号基础版等声明,并修正了生命周期、x86 平台检查与文档示例。旧版错误签名导致的调用会崩溃,升级后请按上方示例调整调用。

发布

dotnet pack -c Release
dotnet nuget push bin\Release\QianTools.*.nupkg --source https://api.nuget.org/v3/index.json
Product Compatible and additional computed target framework versions.
.NET net6.0-windows7.0 is compatible.  net7.0-windows was computed.  net8.0-windows was computed.  net8.0-windows7.0 is compatible.  net9.0-windows was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.7.1 0 10/23/2026
2.7.0 324 7/30/2026
2.6.5 127 7/26/2026
2.6.1 127 7/26/2026
2.6.0 127 7/21/2026
2.5.0 129 7/21/2026
2.4.0 123 7/21/2026
2.3.5 149 5/19/2026
2.3.4 119 5/19/2026
2.3.3 136 5/19/2026
2.3.2 133 5/16/2026
2.3.1 138 5/15/2026
2.3.0 137 5/15/2026
2.1.1 216 12/23/2025
1.1.3 209 12/23/2025
1.1.0 216 12/23/2025
1.0.0 202 7/20/2024

2.7.1:Ezd 模块按官方 EZCAD2 开发者手册校正 9 组原生函数声明(GetPrevBitmap3、位图 4 函数、AddBarCodeToLib、AddCircleTextToLib、连续飞行、EnableLockInputPort、笔号基础版、AddCurve/AddPoint 字符集等),修复初始化状态机、x86 平台检查、DLL 卸载与预览图泄漏;同步更新 README/API 文档与迁移说明。