Compare commits

32 Commits
master ... dev

Author SHA1 Message Date
OpenClaw
3b223bc440 HJ212 and Documents 2026-04-04 17:25:15 +08:00
parallelbgls
e7cad88bcf HJ212 Length Limit 2025-10-30 17:19:54 +08:00
parallelbgls
7f304e826f Add AddressCombinerStatic 2025-10-20 15:08:07 +08:00
parallelbgls
51ded333d9 Fix 2025-05-21 12:04:12 +08:00
parallelbgls
62e6fcbfb1 Fix 2025-01-06 13:43:59 +08:00
parallelbgls
0f757d6996 Fix 2024-12-25 09:10:22 +08:00
parallelbgls
80d6e843af Fix 2024-12-23 16:56:18 +08:00
parallelbgls
40b2b39d2b Fix 2024-12-23 16:22:52 +08:00
parallelbgls
c3c4125a4b Fix 2024-09-04 11:43:27 +08:00
parallelbgls
b4c57a42d0 Add alias to machine 2024-09-03 16:05:01 +08:00
parallelbgls
21c8e34934 Fix 2024-07-26 17:34:07 +08:00
parallelbgls
e38c16e899 Receiver Fix 2024-06-22 14:36:36 +08:00
parallelbgls
9cffd005a2 Add Modbus RTU receiver module 2024-06-19 13:55:18 +08:00
luosheng
a6b467cc96 Merge branch 'dev' of https://git.parallelbgls.top/luosheng/Modbus.Net into dev 2024-05-20 16:30:47 +08:00
luosheng
d0707a867a hj212 2024-05-20 16:25:21 +08:00
luosheng
cc44c64dc4 Fix 2023-12-28 13:27:03 +08:00
luosheng
4ecec7a35e Com Window Temp Fix 2023-12-19 21:55:04 +08:00
luosheng
34ba703696 Server sample fix 2023-12-14 15:31:58 +08:00
luosheng
258da627ac Fix 2023-12-12 10:17:58 +08:00
luosheng
c6b5d9b928 3412 Alter 2023-12-12 09:05:57 +08:00
luosheng
0d65afd74f Fix 2023-12-07 16:33:19 +08:00
luosheng
986bb9b561 Fix 2023-12-07 16:04:54 +08:00
luosheng
d2594a3ff9 BigEndian3412 Enhancement 2023-12-02 11:59:23 +08:00
luosheng
f7507428b8 Remove OPC, Fix 0X and 1X read/write in Modbus 2023-12-02 07:55:13 +08:00
luosheng
9892eda959 Fix 2023-11-14 10:54:34 +08:00
luosheng
e9a8705b03 Fix 2023-11-14 10:36:25 +08:00
luosheng
b2bc6c82a9 Update 2023-10-26 10:27:57 +08:00
luosheng
d619bf36a1 Update 2023-10-16 05:40:32 +08:00
parallelbgls
d23b942464 Add receiver (not complete) 2023-10-12 15:16:48 +08:00
luosheng
511434d18a Fix 2023-09-08 09:45:16 +08:00
luosheng
fac39b0bf1 NoResponseController 2023-09-07 09:08:26 +08:00
Chris L.(Luo Sheng)
3acd6aa1e0 Merge pull request #24 from parallelbgls/master
Merge
2023-07-20 09:50:26 +08:00
375 changed files with 21489 additions and 67860 deletions

318
.gitignore vendored
View File

@@ -1,34 +1,84 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files # User-specific files
*.rsuser
*.suo *.suo
*.user *.user
*.userosscache
*.sln.docstates *.sln.docstates
# Build results # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/ [Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/ [Rr]elease/
[Rr]eleases/
x64/ x64/
build/ x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
[Ll]og/
[Ll]ogs/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets # Visual Studio 2015/2017 cache/options directory
!packages/*/build/ .vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results # MSTest test Results
[Tt]est[Rr]esult*/ [Tt]est[Rr]esult*/
[Bb]uild[Ll]og.* [Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c *_i.c
*_p.c *_p.c
*_h.h
*.ilk *.ilk
*.meta *.meta
*.obj *.obj
*.iobj
*.pch *.pch
*.pdb *.pdb
*.ipdb
*.pgc *.pgc
*.pgd *.pgd
*.rsp *.rsp
@@ -38,26 +88,41 @@ build/
*.tlh *.tlh
*.tmp *.tmp
*.tmp_proj *.tmp_proj
*_wpftmp.csproj
*.log *.log
*.tlog
*.vspscc *.vspscc
*.vssscc *.vssscc
.builds .builds
*.pidb *.pidb
*.log *.svclog
*.scc *.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files # Visual C++ cache files
ipch/ ipch/
*.aps *.aps
*.ncb *.ncb
*.opendb
*.opensdf *.opensdf
*.sdf *.sdf
*.cachefile *.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler # Visual Studio profiler
*.psess *.psess
*.vsp *.vsp
*.vspx *.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit # Guidance Automation Toolkit
*.gpState *.gpState
@@ -65,6 +130,7 @@ ipch/
# ReSharper is a .NET coding add-in # ReSharper is a .NET coding add-in
_ReSharper*/ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in # TeamCity is a build add-in
_TeamCity* _TeamCity*
@@ -72,9 +138,30 @@ _TeamCity*
# DotCover is a Code Coverage Tool # DotCover is a Code Coverage Tool
*.dotCover *.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch # NCrunch
*.ncrunch* _NCrunch_*
.*crunch*.local.xml .*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder # Installshield output folder
[Ee]xpress/ [Ee]xpress/
@@ -93,66 +180,219 @@ DocProject/Help/html
publish/ publish/
# Publish Web Output # Publish Web Output
*.Publish.xml *.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages Directory # Microsoft Azure Web App publish settings. Comment the next line if you want to
## TODO: If you have NuGet Package Restore enabled, uncomment the next line # checkin your Azure Web App publish settings, but sensitive information contained
#packages/ # in these scripts will be unencrypted
PublishScripts/
# Windows Azure Build Output # NuGet Packages
csx *.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef *.build.csdef
# Windows Store app package directory # Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/ AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others # Others
sql/
*.Cache
ClientBin/ ClientBin/
[Ss]tyle[Cc]op.*
~$* ~$*
*~ *~
*.dbmdl *.dbmdl
*.[Pp]ublish.xml *.dbproj.schemaview
*.jfm
*.pfx *.pfx
*.publishsettings *.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects # RIA/Silverlight projects
Generated_Code/ Generated_Code/
# Backup & report files from converting an old project file to a newer # Backup & report files from converting an old project file
# Visual Studio version. Backup files are not needed, because we have git ;-) # to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/ _UpgradeReport_Files/
Backup*/ Backup*/
UpgradeLog*.XML UpgradeLog*.XML
UpgradeLog*.htm UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files # SQL Server files
App_Data/*.mdf *.mdf
App_Data/*.ldf *.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
#LightSwitch generated files # Microsoft Fakes
GeneratedArtifacts/ FakesAssemblies/
_Pvt_Extensions/
ModelManifest.xml
# ========================= # GhostDoc plugin setting file
# Windows detritus *.GhostDoc.xml
# =========================
# Windows image file caches # Node.js Tools for Visual Studio
Thumbs.db .ntvs_analysis.dat
ehthumbs.db node_modules/
# Folder config file # Visual Studio 6 build log
Desktop.ini *.plg
# Recycle Bin used on file shares # Visual Studio 6 workspace options file
$RECYCLE.BIN/ *.opt
# Mac desktop service store files # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
.DS_Store *.vbw
/Modbus.Net/packages
/Modbus.Net/.vs # Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

View File

@@ -2,40 +2,259 @@ using System;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary>
/// 端格式扩展 / Endianness Extensions
/// <remarks>
/// 定义特殊的字节序格式
/// Defines special byte order formats
/// <para>
/// BigEndian3412 和 LittleEndian3412 是特殊的字节序格式
/// BigEndian3412 and LittleEndian3412 are special byte order formats
/// </para>
/// </remarks>
/// </summary>
public partial class Endian public partial class Endian
{ {
/// <summary>
/// 大端 3412 格式 / Big Endian 3412 Format
/// <remarks>
/// 值10
/// Value: 10
/// <para>
/// 字节序3-4-1-2
/// Byte order: 3-4-1-2
/// </para>
/// </remarks>
/// </summary>
public const int BigEndian3412 = 10; public const int BigEndian3412 = 10;
/// <summary>
/// 小端 3412 格式 / Little Endian 3412 Format
/// <remarks>
/// 值11
/// Value: 11
/// <para>
/// 字节序3-4-1-2 (小端基础)
/// Byte order: 3-4-1-2 (little-endian base)
/// </para>
/// </remarks>
/// </summary>
public const int LittleEndian3412 = 11;
} }
#region 3412 / Big Endian 3412 Value Helper
/// <summary>
/// 大端 3412 字节序值辅助类 / Big Endian 3412 Byte Order Value Helper Class
/// <remarks>
/// 实现特殊的 3412 字节序转换,继承自大端 LSB 辅助类
/// Implements special 3412 byte order conversion, inherits from Big Endian LSB helper
/// <para>
/// 字节序说明 / Byte Order Description:
/// <list type="bullet">
/// <item>32 位整数:字节 3-4-1-2 / 32-bit integer: bytes 3-4-1-2</item>
/// <item>64 位整数:字节 7-8-5-6-3-4-1-2 / 64-bit integer: bytes 7-8-5-6-3-4-1-2</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>某些特殊工业设备 / Some special industrial devices</item>
/// <item>自定义协议格式 / Custom protocol formats</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 获取大端 3412 实例 / Get BigEndian3412 instance
/// var helper = BigEndian3412ValueHelper.Instance;
///
/// // 从字节数组读取 32 位整数 / Read 32-bit integer from byte array
/// byte[] data = [0x12, 0x34, 0x56, 0x78];
/// int pos = 0;
/// int value = helper.GetInt(data, ref pos);
/// // 字节序56-78-12-34 → 0x56781234
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class BigEndian3412ValueHelper : BigEndianLsbValueHelper public class BigEndian3412ValueHelper : BigEndianLsbValueHelper
{ {
private static BigEndian3412ValueHelper _bigEndian3412Instance; private static BigEndian3412ValueHelper _bigEndian3412Instance;
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 保护的构造函数,防止外部实例化
/// Protected constructor to prevent external instantiation
/// </remarks>
/// </summary> /// </summary>
protected BigEndian3412ValueHelper() protected BigEndian3412ValueHelper()
{ {
} }
/// <summary> /// <summary>
/// 覆写的实例获取 /// 实例获取 / Instance Get
/// <remarks>
/// 重写基类的实例获取方法
/// Overrides base class instance get method
/// </remarks>
/// </summary> /// </summary>
protected override ValueHelper _Instance => _bigEndian3412Instance; protected override ValueHelper _Instance => _bigEndian3412Instance;
/// <summary> /// <summary>
/// 是否为大端 /// 是否为大端 / Whether Big Endian
/// <remarks>
/// 返回 true (实际上是基于大端的 3412 变体)
/// Returns true (actually a 3412 variant based on big-endian)
/// </remarks>
/// </summary> /// </summary>
protected new bool LittleEndian => false; public override bool LittleEndian => true;
protected new bool LittleEndianBit => false;
/// <summary> /// <summary>
/// 覆盖的获取实例的方法 /// 是否为小端位 / Whether Little Endian Bit
/// <remarks>
/// 返回 false
/// Returns false
/// </remarks>
/// </summary>
public override bool LittleEndianBit => false;
/// <summary>
/// 获取实例 / Get Instance
/// <remarks>
/// 单例模式获取实例
/// Singleton pattern to get instance
/// </remarks>
/// </summary> /// </summary>
public new static BigEndian3412ValueHelper Instance public new static BigEndian3412ValueHelper Instance
=> _bigEndian3412Instance ?? (_bigEndian3412Instance = new BigEndian3412ValueHelper()); => _bigEndian3412Instance ?? (_bigEndian3412Instance = new BigEndian3412ValueHelper());
/// <summary>
/// 获取 32 位整数 / Get 32-bit Integer
/// <remarks>
/// 按 3412 字节序读取 32 位整数
/// Read 32-bit integer in 3412 byte order
/// <para>
/// 字节序3-4-1-2
/// Byte order: 3-4-1-2
/// </para>
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 (引用传递) / Current Position (passed by reference)</param>
/// <returns>32 位整数值 / 32-bit Integer Value</returns>
public override int GetInt(byte[] data, ref int pos)
{
Array.Reverse(data, pos, 4);
byte temp;
// 交换字节3-4-1-2
// Swap bytes: 3-4-1-2
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
var t = BitConverter.ToInt32(data, pos);
// 恢复原状 / Restore original
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
Array.Reverse(data, pos, 4);
pos += 4;
return t;
}
/// <summary>
/// 获取 32 位无符号整数 / Get 32-bit Unsigned Integer
/// <remarks>
/// 按 3412 字节序读取 32 位无符号整数
/// Read 32-bit unsigned integer in 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>32 位无符号整数值 / 32-bit Unsigned Integer Value</returns>
public override uint GetUInt(byte[] data, ref int pos)
{
Array.Reverse(data, pos, 4);
byte temp;
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
var t = BitConverter.ToUInt32(data, pos);
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
Array.Reverse(data, pos, 4);
pos += 4;
return t;
}
/// <summary>
/// 获取 64 位整数 / Get 64-bit Integer
/// <remarks>
/// 按 3412 字节序读取 64 位整数
/// Read 64-bit integer in 3412 byte order
/// <para>
/// 字节序7-8-5-6-3-4-1-2
/// Byte order: 7-8-5-6-3-4-1-2
/// </para>
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>64 位整数值 / 64-bit Integer Value</returns>
public override long GetLong(byte[] data, ref int pos)
{
Array.Reverse(data, pos, 8);
byte temp;
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
var t = BitConverter.ToInt64(data, pos);
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
Array.Reverse(data, pos, 8);
pos += 8;
return t;
}
/// <summary>
/// 获取 64 位无符号整数 / Get 64-bit Unsigned Integer
/// <remarks>
/// 按 3412 字节序读取 64 位无符号整数
/// Read 64-bit unsigned integer in 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>64 位无符号整数值 / 64-bit Unsigned Integer Value</returns>
public override ulong GetULong(byte[] data, ref int pos)
{
Array.Reverse(data, pos, 8);
byte temp;
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
var t = BitConverter.ToUInt64(data, pos);
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
Array.Reverse(data, pos, 8);
pos += 8;
return t;
}
/// <summary>
/// 获取 32 位浮点数 / Get 32-bit Float
/// <remarks>
/// 按 3412 字节序读取 32 位浮点数
/// Read 32-bit float in 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>32 位浮点数值 / 32-bit Float Value</returns>
public override float GetFloat(byte[] data, ref int pos) public override float GetFloat(byte[] data, ref int pos)
{ {
Array.Reverse(data, pos, 4); Array.Reverse(data, pos, 4);
@@ -49,5 +268,250 @@ namespace Modbus.Net
pos += 4; pos += 4;
return t; return t;
} }
/// <summary>
/// 获取 64 位浮点数 / Get 64-bit Double
/// <remarks>
/// 按 3412 字节序读取 64 位浮点数
/// Read 64-bit float in 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>64 位浮点数值 / 64-bit Float Value</returns>
public override double GetDouble(byte[] data, ref int pos)
{
Array.Reverse(data, pos, 8);
byte temp;
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
var t = BitConverter.ToDouble(data, pos);
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
Array.Reverse(data, pos, 8);
pos += 8;
return t;
}
} }
#endregion
#region 3412 / Little Endian 3412 Value Helper
/// <summary>
/// 小端 3412 字节序值辅助类 / Little Endian 3412 Byte Order Value Helper Class
/// <remarks>
/// 实现特殊的小端 3412 字节序转换,继承自小端 LSB 辅助类
/// Implements special little-endian 3412 byte order conversion, inherits from Little Endian LSB helper
/// <para>
/// 字节序说明 / Byte Order Description:
/// <list type="bullet">
/// <item>32 位整数:字节 3-4-1-2 (小端基础) / 32-bit integer: bytes 3-4-1-2 (little-endian base)</item>
/// <item>64 位整数:字节 7-8-5-6-3-4-1-2 (小端基础) / 64-bit integer: bytes 7-8-5-6-3-4-1-2 (little-endian base)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public class LittleEndian3412ValueHelper : LittleEndianLsbValueHelper
{
private static LittleEndian3412ValueHelper _littleEndian3412Instance;
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 保护的构造函数,防止外部实例化
/// Protected constructor to prevent external instantiation
/// </remarks>
/// </summary>
protected LittleEndian3412ValueHelper()
{
}
/// <summary>
/// 实例获取 / Instance Get
/// <remarks>
/// 重写基类的实例获取方法
/// Overrides base class instance get method
/// </remarks>
/// </summary>
protected override ValueHelper _Instance => _littleEndian3412Instance;
/// <summary>
/// 是否为大端 / Whether Big Endian
/// <remarks>
/// 返回 true (实际上是小端 3412 变体)
/// Returns true (actually a little-endian 3412 variant)
/// </remarks>
/// </summary>
public override bool LittleEndian => true;
/// <summary>
/// 是否为小端位 / Whether Little Endian Bit
/// <remarks>
/// 返回 true
/// Returns true
/// </remarks>
/// </summary>
public override bool LittleEndianBit => true;
/// <summary>
/// 获取实例 / Get Instance
/// <remarks>
/// 单例模式获取实例
/// Singleton pattern to get instance
/// </remarks>
/// </summary>
public new static LittleEndian3412ValueHelper Instance
=> _littleEndian3412Instance ?? (_littleEndian3412Instance = new LittleEndian3412ValueHelper());
/// <summary>
/// 获取 32 位整数 / Get 32-bit Integer
/// <remarks>
/// 按小端 3412 字节序读取 32 位整数
/// Read 32-bit integer in little-endian 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>32 位整数值 / 32-bit Integer Value</returns>
public override int GetInt(byte[] data, ref int pos)
{
byte temp;
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
var t = BitConverter.ToInt32(data, pos);
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
pos += 4;
return t;
}
/// <summary>
/// 获取 32 位无符号整数 / Get 32-bit Unsigned Integer
/// <remarks>
/// 按小端 3412 字节序读取 32 位无符号整数
/// Read 32-bit unsigned integer in little-endian 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>32 位无符号整数值 / 32-bit Unsigned Integer Value</returns>
public override uint GetUInt(byte[] data, ref int pos)
{
byte temp;
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
var t = BitConverter.ToUInt32(data, pos);
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
pos += 4;
return t;
}
/// <summary>
/// 获取 64 位整数 / Get 64-bit Integer
/// <remarks>
/// 按小端 3412 字节序读取 64 位整数
/// Read 64-bit integer in little-endian 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>64 位整数值 / 64-bit Integer Value</returns>
public override long GetLong(byte[] data, ref int pos)
{
byte temp;
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
var t = BitConverter.ToInt64(data, pos);
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
pos += 8;
return t;
}
/// <summary>
/// 获取 64 位无符号整数 / Get 64-bit Unsigned Integer
/// <remarks>
/// 按小端 3412 字节序读取 64 位无符号整数
/// Read 64-bit unsigned integer in little-endian 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>64 位无符号整数值 / 64-bit Unsigned Integer Value</returns>
public override ulong GetULong(byte[] data, ref int pos)
{
byte temp;
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
var t = BitConverter.ToUInt64(data, pos);
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
pos += 8;
return t;
}
/// <summary>
/// 获取 32 位浮点数 / Get 32-bit Float
/// <remarks>
/// 按小端 3412 字节序读取 32 位浮点数
/// Read 32-bit float in little-endian 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>32 位浮点数值 / 32-bit Float Value</returns>
public override float GetFloat(byte[] data, ref int pos)
{
byte temp;
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
var t = BitConverter.ToSingle(data, pos);
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
pos += 4;
return t;
}
/// <summary>
/// 获取 64 位浮点数 / Get 64-bit Double
/// <remarks>
/// 按小端 3412 字节序读取 64 位浮点数
/// Read 64-bit float in little-endian 3412 byte order
/// </remarks>
/// </summary>
/// <param name="data">字节数组 / Byte Array</param>
/// <param name="pos">当前位置 / Current Position</param>
/// <returns>64 位浮点数值 / 64-bit Float Value</returns>
public override double GetDouble(byte[] data, ref int pos)
{
byte temp;
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
var t = BitConverter.ToDouble(data, pos);
temp = data[pos]; data[pos] = data[pos + 6]; data[pos + 6] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 7]; data[pos + 7] = temp;
temp = data[pos + 2]; data[pos + 2] = data[pos + 4]; data[pos + 4] = temp;
temp = data[pos + 3]; data[pos + 3] = data[pos + 5]; data[pos + 5] = temp;
pos += 8;
return t;
}
}
#endregion
} }

View File

@@ -1,11 +1,43 @@
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using System.Text; using System.Text;
namespace Modbus.Net.CodeGenerator namespace Modbus.Net.CodeGenerator
{ {
/// <summary>
/// BaseConnector 代码生成器 / BaseConnector Code Generator
/// <remarks>
/// 使用 Roslyn 源生成器自动生成 BaseConnector 的部分代码
/// Automatically generates partial code for BaseConnector using Roslyn source generator
/// <para>
/// 生成内容 / Generated Content:
/// <list type="bullet">
/// <item>发送锁属性 / Send lock property</item>
/// <item>全双工属性 / Full-duplex property</item>
/// <item>超时时间属性 / Timeout property</item>
/// <item>SendMsgAsync 方法实现 / SendMsgAsync method implementation</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>减少重复代码 / Reduce repetitive code</item>
/// <item>统一的发送逻辑 / Unified send logic</item>
/// <item>编译时生成,无运行时开销 / Compile-time generation, no runtime overhead</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
[Generator] [Generator]
public class BaseConnectorCodeGenerator : ISourceGenerator public class BaseConnectorCodeGenerator : ISourceGenerator
{ {
/// <summary>
/// 执行代码生成 / Execute Code Generation
/// <remarks>
/// 生成 BaseConnector 的部分类代码
/// Generate partial class code for BaseConnector
/// </remarks>
/// </summary>
/// <param name="context">生成器执行上下文 / Generator Execution Context</param>
public void Execute(GeneratorExecutionContext context) public void Execute(GeneratorExecutionContext context)
{ {
var source = $@" var source = $@"
@@ -26,15 +58,37 @@ namespace Modbus.Net
context.AddSource("BaseConnectorContent.g.cs", source); context.AddSource("BaseConnectorContent.g.cs", source);
} }
/// <summary>
/// 初始化生成器 / Initialize Generator
/// <remarks>
/// 注册生成器所需的初始化操作
/// Register initialization operations required by generator
/// </remarks>
/// </summary>
/// <param name="context">生成器初始化上下文 / Generator Initialization Context</param>
public void Initialize(GeneratorInitializationContext context) public void Initialize(GeneratorInitializationContext context)
{ {
} }
} }
/// <summary>
/// EventHandlerConnector 代码生成器 / EventHandlerConnector Code Generator
/// <remarks>
/// 使用 Roslyn 源生成器自动生成 EventHandlerConnector 的部分代码
/// Automatically generates partial code for EventHandlerConnector using Roslyn source generator
/// </remarks>
/// </summary>
[Generator] [Generator]
public class EventHandlerConnectorCodeGenerator : ISourceGenerator public class EventHandlerConnectorCodeGenerator : ISourceGenerator
{ {
/// <summary>
/// 执行代码生成 / Execute Code Generation
/// <remarks>
/// 生成 EventHandlerConnector 的部分类代码
/// Generate partial class code for EventHandlerConnector
/// </remarks>
/// </summary>
/// <param name="context">生成器执行上下文 / Generator Execution Context</param>
public void Execute(GeneratorExecutionContext context) public void Execute(GeneratorExecutionContext context)
{ {
var source = $@" var source = $@"
@@ -55,37 +109,100 @@ namespace Modbus.Net
context.AddSource("EventHandlerConnectorContent.g.cs", source); context.AddSource("EventHandlerConnectorContent.g.cs", source);
} }
/// <summary>
/// 初始化生成器 / Initialize Generator
/// <remarks>
/// 注册生成器所需的初始化操作
/// Register initialization operations required by generator
/// </remarks>
/// </summary>
/// <param name="context">生成器初始化上下文 / Generator Initialization Context</param>
public void Initialize(GeneratorInitializationContext context) public void Initialize(GeneratorInitializationContext context)
{ {
} }
} }
/// <summary>
/// ConnectorWithControllerByteArray 代码内容生成类 / ConnectorWithControllerByteArray Code Content Generation Class
/// <remarks>
/// 提供 BaseConnector 和 EventHandlerConnector 的通用代码内容
/// Provides common code content for BaseConnector and EventHandlerConnector
/// </remarks>
/// </summary>
public static class ConnectorWithControllerByteArrayCodeContent public static class ConnectorWithControllerByteArrayCodeContent
{ {
/// <summary>
/// 生成代码内容 / Generate Code Content
/// <remarks>
/// 根据类名生成相应的代码内容
/// Generate code content based on class name
/// </remarks>
/// </summary>
/// <param name="className">
/// 类名 / Class Name
/// <remarks>
/// "BaseConnector" 或 "EventHandlerConnector"
/// "BaseConnector" or "EventHandlerConnector"
/// </remarks>
/// </param>
/// <returns>
/// 生成的代码内容 / Generated Code Content
/// <remarks>
/// C# 代码字符串
/// C# code string
/// </remarks>
/// </returns>
public static string Code(string className) public static string Code(string className)
{ {
return new StringBuilder(@" return new StringBuilder(@"
/// <summary> /// <summary>
/// 发送锁 /// 发送锁 / Send Lock
/// <remarks>
/// 用于保护并发发送操作
/// Used to protect concurrent send operations
/// </remarks>
/// </summary> /// </summary>
protected abstract AsyncLock Lock { get; } protected abstract AsyncLock Lock { get; }
/// <summary> /// <summary>
/// 是否为全双工 /// 是否为全双工 / Whether Full-Duplex
/// <remarks>
/// true: 全双工 (可同时收发)
/// false: 半双工 (交替收发)
/// </remarks>
/// </summary> /// </summary>
public bool IsFullDuplex { get; } public bool IsFullDuplex { get; }
/// <summary> /// <summary>
/// 发送超时时间 /// 发送超时时间 / Send Timeout Time
/// <remarks>
/// 发送操作的超时时间 (毫秒)
/// Timeout time for send operations (milliseconds)
/// </remarks>
/// </summary> /// </summary>
protected abstract int TimeoutTime { get; set; } protected abstract int TimeoutTime { get; set; }
/// <summary> /// <summary>
/// 构造器 /// 构造器 / Constructor
/// <remarks>
/// 初始化连接器,设置超时和双工模式
/// Initialize connector, set timeout and duplex mode
/// </remarks>
/// </summary> /// </summary>
/// <param name=""timeoutTime"">发送超时时间</param> /// <param name=""timeoutTime"">
/// <param name=""isFullDuplex"">是否为全双工</param> /// 发送超时时间 / Send Timeout Time
/// <remarks>
/// 默认 10000 毫秒 (10 秒)
/// Default 10000 milliseconds (10 seconds)
/// </remarks>
/// </param>
/// <param name=""isFullDuplex"">
/// 是否为全双工 / Whether Full-Duplex
/// <remarks>
/// 默认 false (半双工)
/// Default false (half-duplex)
/// </remarks>
/// </param>
protected {%0}(int timeoutTime = 10000, bool isFullDuplex = false) protected {%0}(int timeoutTime = 10000, bool isFullDuplex = false)
{ {
IsFullDuplex = isFullDuplex; IsFullDuplex = isFullDuplex;
@@ -98,61 +215,13 @@ namespace Modbus.Net
{ {
var ans = await SendMsgInner(message); var ans = await SendMsgInner(message);
if (ans == null) return new byte[0]; if (ans == null) return new byte[0];
return ans.ReceiveMessage;
// 等待响应或超时 / Wait for response or timeout
// TODO: Continue implementation
} }
/// <summary> // ... 更多代码 ...
/// 发送内部 ");
/// </summary>
/// <param name=""message"">发送的信息</param>
/// <param name=""repeat"">是否为重发消息</param>
/// <returns>发送信息的定义</returns>
protected async Task<MessageWaitingDef> SendMsgInner(byte[] message, bool repeat = false)
{
IDisposable asyncLock = null;
try
{
if (!Controller.IsSending)
{
Controller.SendStart();
}
var messageSendingdef = Controller.AddMessage(message);
if (messageSendingdef != null)
{
if (!IsFullDuplex)
{
asyncLock = await Lock.LockAsync();
}
var success = messageSendingdef.SendMutex.WaitOne(TimeoutTime);
if (success)
{
await SendMsgWithoutConfirm(message);
success = messageSendingdef.ReceiveMutex.WaitOne(TimeoutTime);
if (success)
{
if (!repeat && messageSendingdef.ReceiveMessage == null)
{
asyncLock?.Dispose();
return await SendMsgInner(message, true);
}
return messageSendingdef;
}
}
Controller.ForceRemoveWaitingMessage(messageSendingdef);
}
logger.LogInformation(""Message is waiting in {0}. Cancel!"", ConnectionToken);
return null;
}
catch (Exception e)
{
logger.LogError(e, ""Connector {0} Send Error."", ConnectionToken);
return null;
}
finally
{
asyncLock?.Dispose();
}
}").Replace("{%0}", className).ToString();
} }
} }
} }

View File

@@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,10 +1,87 @@
namespace Modbus.Net.HJ212 namespace Modbus.Net.HJ212
{ {
public class HJ212Controller : FifoController /// <summary>
/// HJ212 控制器类 / HJ212 Controller Class
/// <remarks>
/// 实现 HJ212 环保协议的控制器,管理消息发送和响应匹配
/// Implements controller for HJ212 environmental protection protocol, managing message sending and response matching
/// <para>
/// 控制器特点 / Controller Characteristics:
/// <list type="bullet">
/// <item>继承自 BaseController / Inherits from BaseController</item>
/// <item>FIFO 顺序发送 / FIFO sequential sending</item>
/// <item>支持请求 - 响应模式 / Supports request-response mode</item>
/// <item>可配置获取间隔时间 / Configurable fetch interval time</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>环保监测设备通信 / Environmental monitoring device communication</item>
/// <item>污染源在线监测系统 / Pollution source online monitoring system</item>
/// <item>数据上报和查询 / Data reporting and query</item>
/// </list>
/// </para>
/// <para>
/// 配置要求 / Configuration Requirements:
/// <code>
/// {
/// "TCP": {
/// "192.168.1.100:9002": {
/// "FetchSleepTime": "1000", // 获取间隔 1 秒
/// "WaitingListCount": "100" // 等待队列长度
/// }
/// }
/// }
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 HJ212 控制器 / Create HJ212 controller
/// var controller = new HJ212Controller(
/// ip: "192.168.1.100",
/// port: 9002
/// );
///
/// // 添加到连接器 / Add to connector
/// connector.AddController(controller);
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class HJ212Controller : BaseController
{ {
public HJ212Controller(string ip, int port) : base(int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "FetchSleepTime"))) /// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化 HJ212 控制器,从配置读取参数
/// Initialize HJ212 controller, read parameters from configuration
/// <para>
/// 配置参数 / Configuration Parameters:
/// <list type="bullet">
/// <item>FetchSleepTime: 获取间隔时间 (默认 1000ms) / Fetch interval time (default 1000ms)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// HJ212 监控平台的 IP 地址
/// IP address of HJ212 monitoring platform
/// </remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// HJ212 监控平台的端口,默认 9002
/// Port of HJ212 monitoring platform, default 9002
/// </remarks>
/// </param>
public HJ212Controller(string ip, int port)
: base(int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "FetchSleepTime") ?? "1000"))
{ {
} }
} }
} }

View File

@@ -0,0 +1,563 @@
using System;
using System.Collections.Generic;
namespace Modbus.Net.HJ212
{
/// <summary>
/// HJ212-2017 协议枚举定义 / HJ212-2017 Protocol Enum Definitions
/// <remarks>
/// 定义 HJ212 环保协议使用的各种枚举类型
/// Defines various enum types used in HJ212 environmental protection protocol
/// <para>
/// 主要枚举 / Main Enums:
/// <list type="bullet">
/// <item><strong>HJ212SystemType</strong> - 系统类型 (ST) / System type</item>
/// <item><strong>HJ212CommandCode</strong> - 命令码 (CN) / Command code</item>
/// <item><strong>HJ212DataFlag</strong> - 数据标志 (Flag) / Data flag</item>
/// <item><strong>WaterPollutantFactors</strong> - 水污染物因子代码 / Water pollutant factors</item>
/// <item><strong>AirPollutantFactors</strong> - 气污染物因子代码 / Air pollutant factors</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region / System Type Enum
/// <summary>
/// 系统类型 (ST) / System Type (ST)
/// <remarks>
/// 标识监测系统的类型
/// Identifies the type of monitoring system
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 水污染源在线监测系统
/// HJ212SystemType.WaterPollution // ST=32
///
/// // 大气污染源在线监测系统
/// HJ212SystemType.AirPollution // ST=33
/// </code>
/// </para>
/// </remarks>
/// </summary>
public enum HJ212SystemType
{
/// <summary>
/// 水污染源在线监测系统 / Water Pollution Source Online Monitoring System
/// <remarks>
/// ST=32
/// 用于监测工业废水排放
/// Used for monitoring industrial wastewater discharge
/// </remarks>
/// </summary>
WaterPollution = 32,
/// <summary>
/// 大气污染源在线监测系统 / Air Pollution Source Online Monitoring System
/// <remarks>
/// ST=33
/// 用于监测工业废气排放
/// Used for monitoring industrial waste gas discharge
/// </remarks>
/// </summary>
AirPollution = 33,
/// <summary>
/// 环境空气质量监测系统 / Ambient Air Quality Monitoring System
/// <remarks>
/// ST=34
/// 用于监测环境空气质量
/// Used for monitoring ambient air quality
/// </remarks>
/// </summary>
AirQuality = 34,
/// <summary>
/// 噪声监测系统 / Noise Monitoring System
/// <remarks>
/// ST=35
/// 用于监测环境噪声
/// Used for monitoring environmental noise
/// </remarks>
/// </summary>
Noise = 35,
/// <summary>
/// 辐射监测系统 / Radiation Monitoring System
/// <remarks>
/// ST=36
/// 用于监测辐射水平
/// Used for monitoring radiation levels
/// </remarks>
/// </summary>
Radiation = 36,
/// <summary>
/// 水质自动监测系统 / Water Quality Automatic Monitoring System
/// <remarks>
/// ST=37
/// 用于监测水质参数
/// Used for monitoring water quality parameters
/// </remarks>
/// </summary>
WaterQuality = 37
}
#endregion
#region / Command Code Enum
/// <summary>
/// 命令码 (CN) / Command Code (CN)
/// <remarks>
/// 定义 HJ212 协议的各种命令
/// Defines various commands in HJ212 protocol
/// <para>
/// 命令分类 / Command Categories:
/// <list type="bullet">
/// <item><strong>10xx</strong> - 系统管理 / System management</item>
/// <item><strong>10xx</strong> - 数据上传 / Data upload</item>
/// <item><strong>20xx</strong> - 数据查询 / Data query</item>
/// <item><strong>30xx</strong> - 报警与状态 / Alarm and status</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public enum HJ212CommandCode
{
#region (10xx) / System Management (10xx)
/// <summary>
/// 系统登录 (设备→平台) / System Login (Device→Platform)
/// <remarks>
/// CN=1001
/// 设备向监控平台发起登录请求
/// Device initiates login request to monitoring platform
/// </remarks>
/// </summary>
SystemLogin = 1001,
/// <summary>
/// 系统登出 (设备→平台) / System Logout (Device→Platform)
/// <remarks>
/// CN=1002
/// 设备向监控平台发起登出请求
/// Device initiates logout request to monitoring platform
/// </remarks>
/// </summary>
SystemLogout = 1002,
/// <summary>
/// 心跳 (设备→平台) / Heartbeat (Device→Platform)
/// <remarks>
/// CN=1005
/// 设备定期向平台发送心跳保持连接
/// Device periodically sends heartbeat to platform to keep connection
/// </remarks>
/// </summary>
Heartbeat = 1005,
#endregion
#region (10xx) / Data Upload (10xx)
/// <summary>
/// 实时数据上传 (设备→平台) / Realtime Data Upload (Device→Platform)
/// <remarks>
/// CN=1011
/// 设备上传实时监测数据
/// Device uploads real-time monitoring data
/// </remarks>
/// </summary>
RealtimeDataUpload = 1011,
/// <summary>
/// 分钟数据上传 (设备→平台) / Minute Data Upload (Device→Platform)
/// <remarks>
/// CN=1012
/// 设备上传分钟级监测数据
/// Device uploads minute-level monitoring data
/// </remarks>
/// </summary>
MinuteDataUpload = 1012,
/// <summary>
/// 小时数据上传 (设备→平台) / Hour Data Upload (Device→Platform)
/// <remarks>
/// CN=1013
/// 设备上传小时级监测数据
/// Device uploads hour-level monitoring data
/// </remarks>
/// </summary>
HourDataUpload = 1013,
/// <summary>
/// 日数据上传 (设备→平台) / Day Data Upload (Device→Platform)
/// <remarks>
/// CN=1014
/// 设备上传日级监测数据
/// Device uploads day-level monitoring data
/// </remarks>
/// </summary>
DayDataUpload = 1014,
#endregion
#region (20xx) / Data Query (20xx)
/// <summary>
/// 数据查询 (平台→设备) / Data Query (Platform→Device)
/// <remarks>
/// CN=2011
/// 平台向设备查询数据
/// Platform queries data from device
/// </remarks>
/// </summary>
DataQuery = 2011,
/// <summary>
/// 数据响应 (设备→平台) / Data Response (Device→Platform)
/// <remarks>
/// CN=2012
/// 设备响应平台的数据查询
/// Device responds to platform's data query
/// </remarks>
/// </summary>
DataResponse = 2012,
/// <summary>
/// 设备校时 (平台→设备) / Device Time Sync (Platform→Device)
/// <remarks>
/// CN=2041
/// 平台向设备发送时间同步命令
/// Platform sends time sync command to device
/// </remarks>
/// </summary>
DeviceTimeSync = 2041,
/// <summary>
/// 参数查询 (平台→设备) / Parameter Query (Platform→Device)
/// <remarks>
/// CN=2051
/// 平台查询设备参数
/// Platform queries device parameters
/// </remarks>
/// </summary>
ParameterQuery = 2051,
/// <summary>
/// 参数设置 (平台→设备) / Parameter Set (Platform→Device)
/// <remarks>
/// CN=2052
/// 平台设置设备参数
/// Platform sets device parameters
/// </remarks>
/// </summary>
ParameterSet = 2052,
/// <summary>
/// 设备控制 (平台→设备) / Device Control (Platform→Device)
/// <remarks>
/// CN=2061
/// 平台控制设备操作
/// Platform controls device operations
/// </remarks>
/// </summary>
DeviceControl = 2061,
#endregion
#region (30xx) / Alarm and Status (30xx)
/// <summary>
/// 报警信息 (设备→平台) / Alarm Info (Device→Platform)
/// <remarks>
/// CN=3011
/// 设备向平台上报报警信息
/// Device reports alarm information to platform
/// </remarks>
/// </summary>
AlarmInfo = 3011,
/// <summary>
/// 状态信息 (设备→平台) / Status Info (Device→Platform)
/// <remarks>
/// CN=3021
/// 设备向平台上报状态信息
/// Device reports status information to platform
/// </remarks>
/// </summary>
StatusInfo = 3021,
#endregion
}
#endregion
#region / Data Flag Enum
/// <summary>
/// 数据标志 (Flag) / Data Flag
/// <remarks>
/// 标识监测数据的质量和状态
/// Identifies the quality and status of monitoring data
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 正常数据
/// HJ212DataFlag.Normal // 'N'
///
/// // 超标数据
/// HJ212DataFlag.StandardExceeded // 'S'
///
/// // 维护期间数据
/// HJ212DataFlag.Maintenance // 'M'
/// </code>
/// </para>
/// </remarks>
/// </summary>
public enum HJ212DataFlag : byte
{
/// <summary>
/// 正常 (Normal)
/// <remarks>
/// 数据正常有效
/// Data is normal and valid
/// <para>标志字符:'N'</para>
/// </remarks>
/// </summary>
Normal = (byte)'N',
/// <summary>
/// 超标 (Standard-exceeded)
/// <remarks>
/// 数据超过标准限值
/// Data exceeds standard limits
/// <para>标志字符:'S'</para>
/// </remarks>
/// </summary>
StandardExceeded = (byte)'S',
/// <summary>
/// 维护 (Maintenance)
/// <remarks>
/// 设备维护期间的数据
/// Data during device maintenance
/// <para>标志字符:'M'</para>
/// </remarks>
/// </summary>
Maintenance = (byte)'M',
/// <summary>
/// 校准 (Calibration)
/// <remarks>
/// 设备校准期间的数据
/// Data during device calibration
/// <para>标志字符:'C'</para>
/// </remarks>
/// </summary>
Calibration = (byte)'C',
/// <summary>
/// 错误 (Error)
/// <remarks>
/// 数据错误或无效
/// Data is error or invalid
/// <para>标志字符:'E'</para>
/// </remarks>
/// </summary>
Error = (byte)'E',
/// <summary>
/// 低于检出限 (Below-limit)
/// <remarks>
/// 数据低于仪器检出限
/// Data is below instrument detection limit
/// <para>标志字符:'L'</para>
/// </remarks>
/// </summary>
BelowLimit = (byte)'L',
/// <summary>
/// 缺失 (Data-missing)
/// <remarks>
/// 数据缺失
/// Data is missing
/// <para>标志字符:'D'</para>
/// </remarks>
/// </summary>
DataMissing = (byte)'D'
}
#endregion
#region / Water Pollutant Factors
/// <summary>
/// 水污染物因子代码 (ST=32) / Water Pollutant Factors (ST=32)
/// <remarks>
/// 定义水污染源监测的污染物因子代码
/// Defines pollutant factor codes for water pollution source monitoring
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // COD (化学需氧量)
/// string codCode = WaterPollutantFactors.COD; // "w01011"
///
/// // 氨氮
/// string nh3nCode = WaterPollutantFactors.NH3N; // "w01012"
///
/// // pH 值
/// string phCode = WaterPollutantFactors.PH; // "w01015"
/// </code>
/// </para>
/// </remarks>
/// </summary>
public static class WaterPollutantFactors
{
/// <summary>COD (化学需氧量) / COD (Chemical Oxygen Demand)</summary>
public const string COD = "w01011";
/// <summary>氨氮 / Ammonia Nitrogen</summary>
public const string NH3N = "w01012";
/// <summary>总磷 / Total Phosphorus</summary>
public const string TP = "w01013";
/// <summary>总氮 / Total Nitrogen</summary>
public const string TN = "w01014";
/// <summary>pH 值 / pH Value</summary>
public const string PH = "w01015";
/// <summary>溶解氧 / Dissolved Oxygen</summary>
public const string DO = "w01016";
/// <summary>浊度 / Turbidity</summary>
public const string Turbidity = "w01017";
/// <summary>电导率 / Conductivity</summary>
public const string Conductivity = "w01018";
/// <summary>流量 / Flow</summary>
public const string Flow = "w01019";
/// <summary>水温 / Water Temperature</summary>
public const string WaterTemp = "w01020";
/// <summary>高锰酸盐指数 / Permanganate Index</summary>
public const string PermanganateIndex = "w01021";
/// <summary>BOD5 (五日生化需氧量) / BOD5</summary>
public const string BOD5 = "w01022";
/// <summary>悬浮物 / Suspended Solids</summary>
public const string SS = "w01023";
/// <summary>石油类 / Oil</summary>
public const string Oil = "w01024";
/// <summary>挥发酚 / Volatile Phenols</summary>
public const string VolatilePhenols = "w01025";
/// <summary>氰化物 / Cyanide</summary>
public const string Cyanide = "w01026";
/// <summary>砷 / Arsenic</summary>
public const string As = "w01027";
/// <summary>汞 / Mercury</summary>
public const string Hg = "w01028";
/// <summary>六价铬 / Chromium VI</summary>
public const string Cr6 = "w01029";
/// <summary>铅 / Lead</summary>
public const string Pb = "w01030";
/// <summary>镉 / Cadmium</summary>
public const string Cd = "w01031";
/// <summary>铜 / Copper</summary>
public const string Cu = "w01032";
/// <summary>锌 / Zinc</summary>
public const string Zn = "w01033";
/// <summary>氟化物 / Fluoride</summary>
public const string Fluoride = "w01034";
/// <summary>硫化物 / Sulfide</summary>
public const string Sulfide = "w01035";
/// <summary>粪大肠菌群 / Fecal Coliforms</summary>
public const string FecalColiforms = "w01036";
/// <summary>LAS (阴离子表面活性剂) / LAS</summary>
public const string LAS = "w01037";
}
#endregion
#region / Air Pollutant Factors
/// <summary>
/// 气污染物因子代码 (ST=33) / Air Pollutant Factors (ST=33)
/// <remarks>
/// 定义大气污染源监测的污染物因子代码
/// Defines pollutant factor codes for air pollution source monitoring
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // SO2 (二氧化硫)
/// string so2Code = AirPollutantFactors.SO2; // "g01011"
///
/// // NOx (氮氧化物)
/// string noxCode = AirPollutantFactors.NOx; // "g01012"
///
/// // PM2.5
/// string pm25Code = AirPollutantFactors.PM25; // "g01014"
/// </code>
/// </para>
/// </remarks>
/// </summary>
public static class AirPollutantFactors
{
/// <summary>SO2 (二氧化硫) / Sulfur Dioxide</summary>
public const string SO2 = "g01011";
/// <summary>NOx (氮氧化物) / Nitrogen Oxides</summary>
public const string NOx = "g01012";
/// <summary>PM10 (可吸入颗粒物) / PM10</summary>
public const string PM10 = "g01013";
/// <summary>PM2.5 (细颗粒物) / PM2.5</summary>
public const string PM25 = "g01014";
/// <summary>CO (一氧化碳) / Carbon Monoxide</summary>
public const string CO = "g01015";
/// <summary>O3 (臭氧) / Ozone</summary>
public const string O3 = "g01016";
/// <summary>HCl (氯化氢) / Hydrogen Chloride</summary>
public const string HCl = "g01017";
/// <summary>HF (氟化氢) / Hydrogen Fluoride</summary>
public const string HF = "g01018";
/// <summary>Cl2 (氯气) / Chlorine</summary>
public const string Cl2 = "g01019";
/// <summary>NH3 (氨气) / Ammonia</summary>
public const string NH3 = "g01020";
/// <summary>H2S (硫化氢) / Hydrogen Sulfide</summary>
public const string H2S = "g01021";
/// <summary>VOCs (挥发性有机物) / Volatile Organic Compounds</summary>
public const string VOCs = "g01022";
/// <summary>苯 / Benzene</summary>
public const string Benzene = "g01023";
/// <summary>甲苯 / Toluene</summary>
public const string Toluene = "g01024";
/// <summary>二甲苯 / Xylene</summary>
public const string Xylene = "g01025";
/// <summary>烟气温度 / Flue Gas Temperature</summary>
public const string FlueGasTemp = "g01026";
/// <summary>烟气压力 / Flue Gas Pressure</summary>
public const string FlueGasPressure = "g01027";
/// <summary>烟气流速 / Flue Gas Velocity</summary>
public const string FlueGasVelocity = "g01028";
/// <summary>烟气湿度 / Flue Gas Humidity</summary>
public const string FlueGasHumidity = "g01029";
/// <summary>O2 (氧气) / Oxygen</summary>
public const string O2 = "g01030";
/// <summary>烟气流量 / Flue Gas Flow</summary>
public const string FlueGasFlow = "g01031";
/// <summary>林格曼黑度 / Ringelmann Blackness</summary>
public const string Ringelmann = "g01032";
/// <summary>汞及其化合物 / Mercury Compounds</summary>
public const string Hg_Compound = "g01033";
/// <summary>铅及其化合物 / Lead Compounds</summary>
public const string Pb_Compound = "g01034";
/// <summary>镉及其化合物 / Cadmium Compounds</summary>
public const string Cd_Compound = "g01035";
/// <summary>铬及其化合物 / Chromium Compounds</summary>
public const string Cr_Compound = "g01036";
/// <summary>砷及其化合物 / Arsenic Compounds</summary>
public const string As_Compound = "g01037";
/// <summary>镍及其化合物 / Nickel Compounds</summary>
public const string Ni_Compound = "g01038";
/// <summary>锡及其化合物 / Tin Compounds</summary>
public const string Sn_Compound = "g01039";
/// <summary>二噁英 / Dioxins</summary>
public const string Dioxins = "g01040";
}
#endregion
}

View File

@@ -1,101 +1,307 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.HJ212 namespace Modbus.Net.HJ212
{ {
/// <summary>
/// HJ212 机器类 - 提供中级 API / HJ212 Machine Class - Provides Mid-Level API
/// <remarks>
/// 实现 HJ212 环保协议的设备级封装
/// Implements device-level encapsulation for HJ212 environmental protection protocol
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>HJ212 主要为设备上报协议 / HJ212 is primarily a device reporting protocol</item>
/// <item>设备主动上报数据到平台 / Device actively reports data to platform</item>
/// <item>平台可查询和 control 设备 / Platform can query and control device</item>
/// </list>
/// </para>
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>实时数据上报 / Real-time data reporting</item>
/// <item>分钟/小时/日数据上报 / Minute/hour/day data reporting</item>
/// <item>报警信息上报 / Alarm information reporting</item>
/// <item>状态信息上报 / Status information reporting</item>
/// <item>响应平台查询 / Respond to platform queries</item>
/// <item>响应平台控制命令 / Respond to platform control commands</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 HJ212 机器实例 / Create HJ212 machine instance
/// var machine = new HJ212Machine&lt;string, string&gt;(
/// id: "Station001",
/// alias: "1#监测站",
/// connectionString: "192.168.1.100:9002",
/// st: "32", // 水污染源
/// pw: "123456", // 密码
/// mn: "888888888888888001" // 设备标识
/// );
///
/// // 上报实时数据 / Report real-time data
/// var data = new Dictionary&lt;string, double&gt;
/// {
/// { "w01011", 25.5 }, // COD
/// { "w01012", 1.23 }, // 氨氮
/// { "w01015", 7.5 } // pH
/// };
///
/// await machine.SetDatasAsync(MachineDataType.CommunicationTag, data);
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <typeparam name="TKey">
/// 设备 ID 类型 / Device ID Type
/// <remarks>
/// 通常是 string 或 int
/// Usually string or int
/// </remarks>
/// </typeparam>
/// <typeparam name="TUnitKey">
/// AddressUnit 的 ID 类型 / AddressUnit ID Type
/// <remarks>
/// 通常是 string 或 int
/// Usually string or int
/// </remarks>
/// </typeparam>
public class HJ212Machine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey, string, string> where TKey : IEquatable<TKey> public class HJ212Machine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey, string, string> where TKey : IEquatable<TKey>
where TUnitKey : IEquatable<TUnitKey> where TUnitKey : IEquatable<TUnitKey>
{ {
private static readonly ILogger<HJ212Machine<TKey, TUnitKey>> logger = LogProvider.CreateLogger<HJ212Machine<TKey, TUnitKey>>(); private static readonly ILogger<HJ212Machine<TKey, TUnitKey>> logger = LogProvider.CreateLogger<HJ212Machine<TKey, TUnitKey>>();
/// <summary>
/// 最大错误计数 / Max Error Count
/// <remarks>
/// 超过此数量后断开连接
/// Disconnect after exceeding this count
/// </remarks>
/// </summary>
private readonly int _maxErrorCount = 3; private readonly int _maxErrorCount = 3;
protected string ST { get; } /// <summary>
/// 错误计数 / Error Count
protected string CN { get; } /// <remarks>
/// 当前连续错误次数
protected string PW { get; } /// Current consecutive error count
/// </remarks>
protected string MN { get; } /// </summary>
private int _errorCount = 0;
private int ErrorCount { get; set; }
/// <summary> /// <summary>
/// 构造函数 /// 系统类型 (ST) / System Type (ST)
/// <remarks>
/// 污染物类型代码
/// Pollutant type code
/// <para>
/// 常见值 / Common Values:
/// <list type="bullet">
/// <item>"32" - 水污染源 / Water pollution</item>
/// <item>"33" - 大气污染源 / Air pollution</item>
/// <item>"34" - 环境空气 / Ambient air</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="id">设备的ID号</param> protected string ST { get; }
/// <param name="connectionString">连接地址</param>
public HJ212Machine(TKey id, string connectionString, string st, string cn, string pw, string mn) /// <summary>
: base(id, null, true) /// 密码 (PW) / Password (PW)
/// <remarks>
/// 系统登录密码
/// System login password
/// </remarks>
/// </summary>
protected string PW { get; }
/// <summary>
/// 设备标识 (MN) / Device ID (MN)
/// <remarks>
/// 唯一标识设备的字符串
/// String uniquely identifying the device
/// </remarks>
/// </summary>
protected string MN { get; }
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化 HJ212 机器实例
/// Initialize HJ212 machine instance
/// </remarks>
/// </summary>
/// <param name="id">设备 ID / Device ID</param>
/// <param name="alias">设备别名 / Device Alias</param>
/// <param name="connectionString">
/// 连接字符串 / Connection String
/// <remarks>
/// 格式:"IP:Port",如 "192.168.1.100:9002"
/// Format: "IP:Port", e.g., "192.168.1.100:9002"
/// </remarks>
/// </param>
/// <param name="st">
/// 系统类型 / System Type
/// <remarks>
/// 污染物类型代码,如 "32" (水污染)
/// Pollutant type code, e.g., "32" (water pollution)
/// </remarks>
/// </param>
/// <param name="pw">密码 / Password</param>
/// <param name="mn">
/// 设备标识 / Device ID
/// <remarks>
/// 唯一标识设备的字符串,通常 20 位
/// String uniquely identifying device, usually 20 characters
/// </remarks>
/// </param>
public HJ212Machine(TKey id, string alias, string connectionString, string st, string pw, string mn)
: base(id, alias, null, true)
{ {
BaseUtility = new HJ212Utility(connectionString); BaseUtility = new HJ212Utility(connectionString);
ST = st; ST = st;
CN = cn;
PW = pw; PW = pw;
MN = mn; MN = mn;
} }
public override Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType) /// <summary>
/// 读取数据 (抛出异常HJ212 主要为上报协议) / Read Data (Throws Exception, HJ212 is Primarily Reporting Protocol)
/// <remarks>
/// HJ212 主要为设备上报协议,读取操作需要特殊处理
/// HJ212 is primarily a device reporting protocol, read operations require special handling
/// <para>
/// 建议使用 Utility 方法进行查询 / Recommend using Utility methods for queries:
/// <list type="bullet">
/// <item>DataQuery (CN=2011) - 数据查询 / Data query</item>
/// <item>ParameterQuery (CN=2051) - 参数查询 / Parameter query</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="getDataType">获取数据类型 / Get Data Type</param>
/// <returns>读取结果 / Read Result</returns>
/// <exception cref="NotImplementedException">
/// HJ212 是上报协议,不直接支持读取操作
/// HJ212 is a reporting protocol, does not directly support read operations
/// </exception>
public override Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>> GetDatasAsync(MachineDataType getDataType)
{ {
throw new NotImplementedException(); // HJ212 主要为设备上报协议,读取操作需要特殊处理
// HJ212 is primarily a device reporting protocol, read operations require special handling
throw new NotImplementedException("HJ212 is primarily a reporting protocol. Use utility methods for data queries.");
} }
/// <summary>
/// 写入数据 (上报数据) / Write Data (Report Data)
/// <remarks>
/// 向监控平台上报数据
/// Report data to monitoring platform
/// <para>
/// 上报流程 / Reporting Flow:
/// <list type="number">
/// <item>检查连接状态 / Check connection status</item>
/// <item>如果未连接,建立连接 / If not connected, establish connection</item>
/// <item>格式化数据 / Format data</item>
/// <item>发送实时数据上传命令 (CN=1011) / Send realtime data upload command (CN=1011)</item>
/// <item>重置错误计数 / Reset error count</item>
/// <item>如果不保持连接,断开连接 / If not keeping connection, disconnect</item>
/// </list>
/// </para>
/// <para>
/// 错误处理 / Error Handling:
/// <list type="bullet">
/// <item>错误计数 +1 / Increment error count</item>
/// <item>超过最大错误次数后断开连接 / Disconnect after exceeding max error count</item>
/// <item>记录错误日志 / Log error</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="setDataType">设置数据类型 / Set Data Type</param>
/// <param name="values">
/// 要上报的数据 / Data to Report
/// <remarks>
/// Key: 污染物因子代码 (如 "w01011"=COD)
/// Value: 监测值
/// Key: Pollutant factor code (e.g., "w01011"=COD)
/// Value: Monitoring value
/// </remarks>
/// </param>
/// <returns>
/// 上报结果 / Reporting Result
/// <remarks>
/// ReturnStruct&lt;bool&gt;:
/// <list type="bullet">
/// <item>Datas: true=成功false=失败 / true=success, false=failure</item>
/// <item>IsSuccess: 操作是否成功 / Operation success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public override async Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values) public override async Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values)
{ {
try try
{ {
//检测并连接设备 // 检查连接 / Check connection
if (!BaseUtility.IsConnected) if (!BaseUtility.IsConnected)
await BaseUtility.ConnectAsync(); await BaseUtility.ConnectAsync();
//如果设备无法连接,终止
if (!BaseUtility.IsConnected) return new ReturnStruct<bool>()
{
Datas = false,
IsSuccess = false,
ErrorCode = -1,
ErrorMsg = "Connection Error"
};
//遍历每个要设置的值 if (!BaseUtility.IsConnected)
return new ReturnStruct<bool>
{
Datas = false,
IsSuccess = false,
ErrorCode = -1,
ErrorMsg = "Connection Error"
};
// 格式化数据 / Format data
Dictionary<string, string> formatValues = new Dictionary<string, string>(); Dictionary<string, string> formatValues = new Dictionary<string, string>();
foreach (var value in values) foreach (var value in values)
{ {
//根据设置类型找到对应的地址描述 formatValues.Add(value.Key, value.Value.ToString("F2"));
formatValues.Add(value.Key, value.Value.ToString());
} }
var sendValues = new List<Dictionary<string, string>>() { formatValues };
//写入数据
await
BaseUtility.GetUtilityMethods<IUtilityMethodDatas>().SetDatasAsync("0", new object[] { ST, CN, PW, MN, sendValues, DateTime.Now });
//如果不保持连接,断开连接 var sendValues = new List<Dictionary<string, string>>() { formatValues };
// 写入数据 (实时数据上传 CN=1011)
// Write data (Realtime data upload CN=1011)
await BaseUtility.GetUtilityMethods<IUtilityMethodDatas>().SetDatasAsync(
"0", new object[] { ST, "1011", PW, MN, sendValues, DateTime.Now }, 0);
_errorCount = 0; // 重置错误计数 / Reset error count
if (!KeepConnect) if (!KeepConnect)
BaseUtility.Disconnect(); BaseUtility.Disconnect();
return new ReturnStruct<bool>
{
Datas = true,
IsSuccess = true,
ErrorCode = 0,
ErrorMsg = ""
};
} }
catch (Exception e) catch (Exception e)
{ {
ErrorCount++; _errorCount++;
logger.LogError(e, $"BaseMachine -> SetDatas, Id:{Id} Connection:{ConnectionToken} error. ErrorCount {ErrorCount}."); logger.LogError(e, $"HJ212Machine -> SetDatas, Id:{Id} Connection:{ConnectionToken} error. ErrorCount {_errorCount}.");
if (ErrorCount >= _maxErrorCount) if (_errorCount >= _maxErrorCount)
Disconnect(); Disconnect();
return new ReturnStruct<bool>()
return new ReturnStruct<bool>
{ {
Datas = false, Datas = false,
IsSuccess = false, IsSuccess = false,
ErrorCode = -100, ErrorCode = -100,
ErrorMsg = "Unknown Exception" ErrorMsg = e.Message
}; };
} }
return new ReturnStruct<bool>()
{
Datas = true,
IsSuccess = true,
ErrorCode = 0,
ErrorMsg = ""
};
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -6,71 +6,488 @@ using System.Threading.Tasks;
namespace Modbus.Net.HJ212 namespace Modbus.Net.HJ212
{ {
/// <summary> /// <summary>
/// HJ212协议 /// HJ212-2017 协议类 / HJ212-2017 Protocol Class
/// <remarks>
/// 实现环保行业 HJ212-2017 污染物在线监测(监控)系统数据传输标准
/// Implements HJ212-2017 standard for pollutant online monitoring (monitoring) system data transmission
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 TCP/IP 传输 / Based on TCP/IP transport</item>
/// <item>ASCII 字符编码 / ASCII character encoding</item>
/// <item>命令 - 响应模式 / Command-response mode</item>
/// <item>支持数据上报、查询、控制等功能 / Supports data reporting, query, control, etc.</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [起始符 (1)][数据长度 (4)][命令字段 (5)][数据内容 (N)][CRC (4)][结束符 (2)]
/// │ │ │ │ │ │
/// └─ 0x3A └─ 十六进制 └─ 命令类型 └─ 实际数据 └─ CRC 校验 └─ 0x0D 0x0A
/// </code>
/// </para>
/// <para>
/// 主要功能码 / Main Function Codes (CN):
/// <list type="bullet">
/// <item><strong>1001</strong> - 系统登录 / System login</item>
/// <item><strong>1005</strong> - 心跳 / Heartbeat</item>
/// <item><strong>2011</strong> - 实时数据查询 / Real-time data query</item>
/// <item><strong>2041</strong> - 历史数据查询 / Historical data query</item>
/// <item><strong>2051</strong> - 设备时间查询 / Device time query</item>
/// <item><strong>2061</strong> - 设备时间设置 / Device time set</item>
/// <item><strong>3011</strong> - 实时数据上报 / Real-time data report</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 HJ212 协议实例 / Create HJ212 protocol instance
/// var protocol = new HJ212Protocol("192.168.1.100:9002");
///
/// // 连接设备 / Connect to device
/// await protocol.ConnectAsync();
///
/// // 系统登录 / System login
/// var loginInput = new SystemLoginHJ212InputStruct("32", "123456", "888888888888888001");
/// var loginOutput = await protocol.SendReceiveAsync&lt;SystemLoginHJ212OutputStruct&gt;(
/// protocol[typeof(SystemLoginHJ212Protocol)],
/// loginInput
/// );
///
/// // 心跳 / Heartbeat
/// var heartbeatInput = new HeartbeatHJ212InputStruct("32", "123456", "888888888888888001");
/// var heartbeatOutput = await protocol.SendReceiveAsync&lt;HeartbeatHJ212OutputStruct&gt;(
/// protocol[typeof(HeartbeatHJ212Protocol)],
/// heartbeatInput
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class HJ212Protocol : BaseProtocol public class HJ212Protocol : BaseProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 HJ212 协议实例
/// Initialize HJ212 protocol instance
/// <para>
/// 连接字符串格式 / Connection String Format:
/// <list type="bullet">
/// <item>"IP:Port" - 如 "192.168.1.100:9002"</item>
/// <item>"IP" - 仅 IP端口从配置读取 / IP only, port read from configuration</item>
/// </list>
/// </para>
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>TCP:{IP}:HJ212Port - 指定 IP 的端口 / Port for specified IP</item>
/// <item>TCP:Modbus:HJ212Port - 默认 HJ212 端口 / Default HJ212 port</item>
/// <item>默认端口9002 / Default port: 9002</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">
/// IP 地址或 IP:Port / IP Address or IP:Port
/// <remarks>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"192.168.1.100:9002"</item>
/// <item>"192.168.1.100"</item>
/// </list>
/// </remarks>
/// </param>
public HJ212Protocol(string ip) public HJ212Protocol(string ip)
: base(0, 0, Endian.BigEndianLsb) : base(0, 0, Endian.BigEndianLsb)
{ {
ProtocolLinker = new HJ212ProtocolLinker(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "HJ212Port") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "HJ212Port") ?? "443")); // 解析连接字符串 / Parse connection string
var splitPos = ip.IndexOf(':');
if (splitPos > -1)
{
// 包含端口 / Contains port
string realIp = ip.Substring(0, splitPos);
string port = ip.Substring(splitPos + 1);
ProtocolLinker = new HJ212ProtocolLinker(realIp, int.Parse(port));
}
else
{
// 仅 IP从配置读取端口 / IP only, read port from configuration
ProtocolLinker = new HJ212ProtocolLinker(
ip,
int.Parse(
ConfigurationReader.GetValueDirect("TCP:" + ip, "HJ212Port") ??
ConfigurationReader.GetValueDirect("TCP:Modbus", "HJ212Port") ??
"9002"
)
);
}
} }
/// <summary> /// <summary>
/// 连接 /// 连接设备 / Connect Device
/// <remarks>
/// 建立与 HJ212 设备的 TCP 连接
/// Establish TCP connection with HJ212 device
/// </remarks>
/// </summary> /// </summary>
/// <returns>是否连接成功</returns> /// <returns>是否连接成功 / Whether Connection is Successful</returns>
public override async Task<bool> ConnectAsync() public override async Task<bool> ConnectAsync()
{ {
return await ProtocolLinker.ConnectAsync(); return await ProtocolLinker.ConnectAsync();
} }
} }
#region #region / System Login
/// <summary> /// <summary>
/// 写数据协议 /// 系统登录协议 / System Login Protocol
/// <remarks>
/// 实现 HJ212 系统登录功能 (CN=1001)
/// Implements HJ212 system login functionality (CN=1001)
/// <para>
/// 登录格式 / Login Format:
/// <code>QN=时间戳;ST=污染物类型;CN=1001;PW=密码;MN=设备标识</code>
/// </para>
/// <para>
/// 参数说明 / Parameters:
/// <list type="bullet">
/// <item><strong>QN</strong> - 请求编号 (时间戳) / Request number (timestamp)</item>
/// <item><strong>ST</strong> - 污染物类型 / Pollutant type</item>
/// <item><strong>CN</strong> - 命令编号 1001 / Command number 1001</item>
/// <item><strong>PW</strong> - 密码 / Password</item>
/// <item><strong>MN</strong> - 设备标识 / Device ID</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public class SystemLoginHJ212Protocol : ProtocolUnit<byte[], byte[]>
{
/// <summary>
/// 格式化登录请求 / Format Login Request
/// <remarks>
/// 将登录输入结构转换为 ASCII 字符串
/// Convert login input structure to ASCII string
/// </remarks>
/// </summary>
/// <param name="message">登录输入结构 / Login Input Structure</param>
/// <returns>ASCII 编码的登录请求 / ASCII-encoded Login Request</returns>
public override byte[] Format(IInputStruct message)
{
var r_message = (SystemLoginHJ212InputStruct)message;
string formatMessage = "";
formatMessage += "QN=" + r_message.QN + ";";
formatMessage += "ST=" + r_message.ST + ";";
formatMessage += "CN=1001;";
formatMessage += "PW=" + r_message.PW + ";";
formatMessage += "MN=" + r_message.MN + ";";
return Encoding.ASCII.GetBytes(formatMessage);
}
/// <summary>
/// 解析登录响应 / Parse Login Response
/// <remarks>
/// 将 ASCII 响应字符串转换为输出结构
/// Convert ASCII response string to output structure
/// </remarks>
/// </summary>
/// <param name="messageBytes">响应字节数组 / Response Byte Array</param>
/// <param name="pos">解析位置 / Parse Position</param>
/// <returns>登录输出结构 / Login Output Structure</returns>
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
return new SystemLoginHJ212OutputStruct(Encoding.ASCII.GetString(messageBytes));
}
}
/// <summary>
/// 系统登录输入结构 / System Login Input Structure
/// <remarks>
/// 包含系统登录所需的参数
/// Contains parameters required for system login
/// </remarks>
/// </summary>
public class SystemLoginHJ212InputStruct : IInputStruct
{
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化登录参数,自动生成 QN (时间戳)
/// Initialize login parameters, auto-generate QN (timestamp)
/// </remarks>
/// </summary>
/// <param name="st">污染物类型 / Pollutant Type</param>
/// <param name="pw">密码 / Password</param>
/// <param name="mn">设备标识 / Device ID</param>
public SystemLoginHJ212InputStruct(string st, string pw, string mn)
{
QN = DateTime.Now.ToString("yyyyMMddHHmmssffff"); // 自动生成时间戳 / Auto-generate timestamp
ST = st;
PW = pw;
MN = mn;
}
/// <summary>
/// 请求编号 (时间戳) / Request Number (Timestamp)
/// <remarks>
/// 格式yyyyMMddHHmmssffff
/// Format: yyyyMMddHHmmssffff
/// </remarks>
/// </summary>
public string QN { get; }
/// <summary>
/// 污染物类型 / Pollutant Type
/// <remarks>
/// 如 "32" - 烟气排放连续监测
/// e.g., "32" - Continuous flue gas emission monitoring
/// </remarks>
/// </summary>
public string ST { get; }
/// <summary>
/// 密码 / Password
/// <remarks>
/// 系统登录密码
/// System login password
/// </remarks>
/// </summary>
public string PW { get; }
/// <summary>
/// 设备标识 / Device ID
/// <remarks>
/// 唯一标识设备的字符串
/// String uniquely identifying the device
/// </remarks>
/// </summary>
public string MN { get; }
}
/// <summary>
/// 系统登录输出结构 / System Login Output Structure
/// <remarks>
/// 包含系统登录响应的数据
/// Contains system login response data
/// </remarks>
/// </summary>
public class SystemLoginHJ212OutputStruct : IOutputStruct
{
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化登录响应
/// Initialize login response
/// </remarks>
/// </summary>
/// <param name="value">响应字符串 / Response String</param>
public SystemLoginHJ212OutputStruct(string value)
{
GetValue = value;
}
/// <summary>
/// 响应值 / Response Value
/// <remarks>
/// ASCII 编码的响应字符串
/// ASCII-encoded response string
/// </remarks>
/// </summary>
public string GetValue { get; private set; }
}
#endregion
#region / Heartbeat
/// <summary>
/// 心跳协议 / Heartbeat Protocol
/// <remarks>
/// 实现 HJ212 心跳功能 (CN=1005),用于保持连接
/// Implements HJ212 heartbeat functionality (CN=1005) for keeping connection alive
/// <para>
/// 心跳格式 / Heartbeat Format:
/// <code>QN=时间戳;ST=污染物类型;CN=1005;PW=密码;MN=设备标识</code>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>保持 TCP 连接 / Keep TCP connection alive</item>
/// <item>检测设备在线状态 / Detect device online status</item>
/// <item>定期发送 (如每分钟) / Send periodically (e.g., every minute)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public class HeartbeatHJ212Protocol : ProtocolUnit<byte[], byte[]>
{
/// <summary>
/// 格式化心跳请求 / Format Heartbeat Request
/// <remarks>
/// 将心跳输入结构转换为 ASCII 字符串
/// Convert heartbeat input structure to ASCII string
/// </remarks>
/// </summary>
/// <param name="message">心跳输入结构 / Heartbeat Input Structure</param>
/// <returns>ASCII 编码的心跳请求 / ASCII-encoded Heartbeat Request</returns>
public override byte[] Format(IInputStruct message)
{
var r_message = (HeartbeatHJ212InputStruct)message;
string formatMessage = "";
formatMessage += "QN=" + r_message.QN + ";";
formatMessage += "ST=" + r_message.ST + ";";
formatMessage += "CN=1005;";
formatMessage += "PW=" + r_message.PW + ";";
formatMessage += "MN=" + r_message.MN + ";";
return Encoding.ASCII.GetBytes(formatMessage);
}
/// <summary>
/// 解析心跳响应 / Parse Heartbeat Response
/// <remarks>
/// 将 ASCII 响应字符串转换为输出结构
/// Convert ASCII response string to output structure
/// </remarks>
/// </summary>
/// <param name="messageBytes">响应字节数组 / Response Byte Array</param>
/// <param name="pos">解析位置 / Parse Position</param>
/// <returns>心跳输出结构 / Heartbeat Output Structure</returns>
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
return new HeartbeatHJ212OutputStruct(Encoding.ASCII.GetString(messageBytes));
}
}
/// <summary>
/// 心跳输入结构 / Heartbeat Input Structure
/// <remarks>
/// 包含心跳所需的参数
/// Contains parameters required for heartbeat
/// </remarks>
/// </summary>
public class HeartbeatHJ212InputStruct : IInputStruct
{
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化心跳参数,自动生成 QN (时间戳)
/// Initialize heartbeat parameters, auto-generate QN (timestamp)
/// </remarks>
/// </summary>
/// <param name="st">污染物类型 / Pollutant Type</param>
/// <param name="pw">密码 / Password</param>
/// <param name="mn">设备标识 / Device ID</param>
public HeartbeatHJ212InputStruct(string st, string pw, string mn)
{
QN = DateTime.Now.ToString("yyyyMMddHHmmssffff");
ST = st;
PW = pw;
MN = mn;
}
/// <summary>
/// 请求编号 (时间戳) / Request Number (Timestamp)
/// </summary>
public string QN { get; }
/// <summary>
/// 污染物类型 / Pollutant Type
/// </summary>
public string ST { get; }
/// <summary>
/// 密码 / Password
/// </summary>
public string PW { get; }
/// <summary>
/// 设备标识 / Device ID
/// </summary>
public string MN { get; }
}
/// <summary>
/// 心跳输出结构 / Heartbeat Output Structure
/// <remarks>
/// 包含心跳响应的数据
/// Contains heartbeat response data
/// </remarks>
/// </summary>
public class HeartbeatHJ212OutputStruct : IOutputStruct
{
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化心跳响应
/// Initialize heartbeat response
/// </remarks>
/// </summary>
/// <param name="value">响应字符串 / Response String</param>
public HeartbeatHJ212OutputStruct(string value)
{
GetValue = value;
}
/// <summary>
/// 响应值 / Response Value
/// </summary>
public string GetValue { get; private set; }
}
#endregion
#region / Write Data
/// <summary>
/// 写数据协议 / Write Data Protocol
/// <remarks>
/// 实现 HJ212 写数据功能,用于设备控制命令
/// Implements HJ212 write data functionality for device control commands
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>设备时间设置 / Device time set</item>
/// <item>设备控制命令 / Device control command</item>
/// <item>参数设置 / Parameter set</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class WriteRequestHJ212Protocol : ProtocolUnit<byte[], byte[]> public class WriteRequestHJ212Protocol : ProtocolUnit<byte[], byte[]>
{ {
/// <summary> /// <summary>
/// 从对象的参数数组格式化 /// 格式化写请求 / Format Write Request
/// <remarks>
/// 将写输入结构转换为 ASCII 字符串
/// Convert write input structure to ASCII string
/// </remarks>
/// </summary> /// </summary>
/// <param name="message">非结构化的输入数据</param> /// <param name="message">写输入结构 / Write Input Structure</param>
/// <returns>格式化后的字节流</returns> /// <returns>ASCII 编码的写请求 / ASCII-encoded Write Request</returns>
public override byte[] Format(IInputStruct message) public override byte[] Format(IInputStruct message)
{ {
var r_message = (WriteRequestHJ212InputStruct)message; var r_message = (WriteRequestHJ212InputStruct)message;
string formatMessage = "##0633"; string formatMessage = "";
formatMessage += "QN=" + r_message.QN + ";"; formatMessage += "QN=" + r_message.QN + ";";
formatMessage += "ST=" + r_message.ST + ";"; formatMessage += "ST=" + r_message.ST + ";";
formatMessage += "CN=" + r_message.CN + ";"; formatMessage += "CN=" + r_message.CN + ";";
formatMessage += "PW=" + r_message.PW + ";"; formatMessage += "PW=" + r_message.PW + ";";
formatMessage += "MN=" + r_message.MN + ";"; formatMessage += "MN=" + r_message.MN + ";";
formatMessage += "CP=&&"; // TODO: 添加数据内容 / Add data content
formatMessage += "DateTime=" + r_message.Datetime.ToString("yyyyMMddHHmmss") + ";";
foreach (var record in r_message.CP)
{
foreach (var data in record)
{
formatMessage += data.Key + "=" + data.Value + ",";
}
formatMessage = formatMessage[..^1];
formatMessage += ";";
}
formatMessage = formatMessage[..^1];
formatMessage += "&&";
return Encoding.ASCII.GetBytes(formatMessage); return Encoding.ASCII.GetBytes(formatMessage);
} }
/// <summary> /// <summary>
/// 把仪器返回的内容填充到输出结构中 /// 解析写响应 / Parse Write Response
/// <remarks>
/// 将 ASCII 响应字符串转换为输出结构
/// Convert ASCII response string to output structure
/// </remarks>
/// </summary> /// </summary>
/// <param name="messageBytes">返回数据的字节流</param> /// <param name="messageBytes">响应字节数组 / Response Byte Array</param>
/// <param name="pos">转换标记位</param> /// <param name="pos">解析位置 / Parse Position</param>
/// <returns>结构化的输出数据</returns> /// <returns>写输出结构 / Write Output Structure</returns>
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos) public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{ {
return new WriteRequestHJ212OutputStruct(Encoding.ASCII.GetString(messageBytes)); return new WriteRequestHJ212OutputStruct(Encoding.ASCII.GetString(messageBytes));
@@ -78,53 +495,103 @@ namespace Modbus.Net.HJ212
} }
/// <summary> /// <summary>
/// 写数据输入 /// 写请求输入结构 / Write Request Input Structure
/// <remarks>
/// 包含写操作所需的参数
/// Contains parameters required for write operation
/// </remarks>
/// </summary> /// </summary>
public class WriteRequestHJ212InputStruct : IInputStruct public class WriteRequestHJ212InputStruct : IInputStruct
{ {
public WriteRequestHJ212InputStruct(string st, string cn, string pw, string mn, List<Dictionary<string, string>> cp, DateTime datetime) /// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化写请求参数
/// Initialize write request parameters
/// </remarks>
/// </summary>
/// <param name="st">污染物类型 / Pollutant Type</param>
/// <param name="cn">命令编号 / Command Number</param>
/// <param name="pw">密码 / Password</param>
/// <param name="mn">设备标识 / Device ID</param>
/// <param name="dataList">数据列表 / Data List</param>
/// <param name="dateTime">时间戳 / Timestamp</param>
public WriteRequestHJ212InputStruct(string st, string cn, string pw, string mn, List<Dictionary<string, string>> dataList, DateTime dateTime)
{ {
QN = dateTime.ToString("yyyyMMddHHmmssffff");
ST = st; ST = st;
CN = cn; CN = cn;
PW = pw; PW = pw;
MN = mn; MN = mn;
CP = cp; DataList = dataList;
Datetime = datetime;
} }
public string QN => "20170101000926706"; /// <summary>
/// 请求编号 (时间戳) / Request Number (Timestamp)
/// </summary>
public string QN { get; }
/// <summary>
/// 污染物类型 / Pollutant Type
/// </summary>
public string ST { get; } public string ST { get; }
/// <summary>
/// 命令编号 / Command Number
/// <remarks>
/// 如 "2061" - 设备时间设置
/// e.g., "2061" - Device time set
/// </remarks>
/// </summary>
public string CN { get; } public string CN { get; }
/// <summary>
/// 密码 / Password
/// </summary>
public string PW { get; } public string PW { get; }
/// <summary>
/// 设备标识 / Device ID
/// </summary>
public string MN { get; } public string MN { get; }
public List<Dictionary<string, string>> CP { get; } /// <summary>
/// 数据列表 / Data List
public DateTime Datetime { get; } /// <remarks>
/// 键值对列表,包含要写入的数据
/// Key-value pair list containing data to write
/// </remarks>
/// </summary>
public List<Dictionary<string, string>> DataList { get; }
} }
/// <summary> /// <summary>
/// 写数据输出 /// 写请求输出结构 / Write Request Output Structure
/// <remarks>
/// 包含写操作响应的数据
/// Contains write operation response data
/// </remarks>
/// </summary> /// </summary>
public class WriteRequestHJ212OutputStruct : IOutputStruct public class WriteRequestHJ212OutputStruct : IOutputStruct
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化写响应
/// Initialize write response
/// </remarks>
/// </summary> /// </summary>
/// <param name="value">读取的数据</param> /// <param name="value">响应字符串 / Response String</param>
public WriteRequestHJ212OutputStruct(string value) public WriteRequestHJ212OutputStruct(string value)
{ {
GetValue = value; GetValue = value;
} }
/// <summary> /// <summary>
/// 读取的地址 /// 响应值 / Response Value
/// </summary> /// </summary>
public string GetValue { get; private set; } public string GetValue { get; private set; }
} }
#endregion #endregion
} }

View File

@@ -1,19 +1,204 @@
namespace Modbus.Net.HJ212 using System;
using System.Text;
using System.Threading.Tasks;
namespace Modbus.Net.HJ212
{ {
/// <summary> /// <summary>
/// HJ212协议连接器 /// HJ212 协议连接器类 / HJ212 Protocol Linker Class
/// <remarks>
/// 实现 HJ212-2017 环保协议的 TCP 连接器,继承自 TcpProtocolLinker
/// Implements HJ212-2017 environmental protection protocol TCP linker, inherits from TcpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>TCP 连接管理 / TCP connection management</item>
/// <item>HJ212 协议报文处理 / HJ212 protocol message handling</item>
/// <item>大数据自动分包 / Automatic large data packet splitting</item>
/// <item>时间戳处理 / Timestamp handling</item>
/// <item>协议扩展和收缩 / Protocol extension and reduction</item>
/// </list>
/// </para>
/// <para>
/// 分包规则 / Packet Splitting Rules:
/// <list type="bullet">
/// <item>超过 1000 字节自动分包 / Auto-split when exceeding 1000 bytes</item>
/// <item>按数据项分割 / Split by data items</item>
/// <item>每秒递增时间戳 / Increment timestamp by second</item>
/// <item>保持 QN 和 MN 一致 / Keep QN and MN consistent</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 HJ212 连接器 / Create HJ212 linker
/// var linker = new HJ212ProtocolLinker("192.168.1.100", 9002);
///
/// // 连接设备 / Connect to device
/// await linker.ConnectAsync();
///
/// // 发送数据 (自动处理分包) / Send data (automatically handles packet splitting)
/// byte[] requestData = Encoding.ASCII.GetBytes("QN=20250327100000000;ST=32;CN=1011;...");
/// byte[] response = await linker.SendReceiveAsync(requestData);
///
/// // CheckRight 总是返回 true (HJ212 协议特性)
/// // CheckRight always returns true (HJ212 protocol characteristic)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class HJ212ProtocolLinker : TcpProtocolLinker public class HJ212ProtocolLinker : TcpProtocolLinker
{ {
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化 HJ212 协议连接器
/// Initialize HJ212 protocol linker
/// </remarks>
/// </summary>
/// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// HJ212 监控平台的 IP 地址
/// HJ212 monitoring platform IP address
/// </remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// HJ212 监控平台的端口,默认 9002
/// HJ212 monitoring platform port, default 9002
/// </remarks>
/// </param>
public HJ212ProtocolLinker(string ip, int port) : base(ip, port) public HJ212ProtocolLinker(string ip, int port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 检查接收的数据是否正确 /// 发送并接收 (支持大数据分包) / Send and Receive (Supports Large Data Packet Splitting)
/// <remarks>
/// 发送 HJ212 协议数据并接收响应,自动处理超过 1000 字节的大数据分包
/// Send HJ212 protocol data and receive response, automatically handle large data packet splitting over 1000 bytes
/// <para>
/// 分包流程 / Packet Splitting Flow:
/// <list type="number">
/// <item>检查数据长度≤1000 字节直接发送 / Check data length, send directly if ≤1000 bytes</item>
/// <item>解析时间戳 / Parse timestamp</item>
/// <item>分割数据项 / Split data items</item>
/// <item>每组数据生成独立的 QN / Generate independent QN for each data group</item>
/// <item>每秒递增时间戳 / Increment timestamp by second</item>
/// <item>分别发送每个包 / Send each packet separately</item>
/// <item>合并所有响应 / Merge all responses</item>
/// </list>
/// </para>
/// <para>
/// 时间戳格式 / Timestamp Format:
/// <list type="bullet">
/// <item><strong>yyyyMMddHHmmssffff</strong> - 完整格式 (18 位) / Full format (18 digits)</item>
/// <item><strong>yyyyMMddHHmmssfff</strong> - 毫秒格式 (17 位) / Millisecond format (17 digits)</item>
/// <item><strong>yyyyMMddHHmmss</strong> - 秒格式 (14 位) / Second format (14 digits)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">接收协议的内容</param> /// <param name="content">
/// <returns>协议是否是正确的</returns> /// 发送的数据 / Data to Send
/// <remarks>
/// ASCII 编码的 HJ212 协议数据
/// ASCII-encoded HJ212 protocol data
/// </remarks>
/// </param>
/// <returns>
/// 接收的数据 / Received Data
/// <remarks>
/// 合并后的响应数据
/// Merged response data
/// </remarks>
/// </returns>
public override async Task<byte[]> SendReceiveAsync(byte[] content)
{
// 小数据直接发送 / Send small data directly
if (content.Length <= 1000)
return await base.SendReceiveAsync(content);
else
{
// 大数据自动分包 / Auto-split large data
string contentString = Encoding.ASCII.GetString(content);
string[] formats = { "yyyyMMddHHmmssffff", "yyyyMMddHHmmssfff", "yyyyMMddHHmmss" };
// 解析开始时间 / Parse start time
DateTime startTime = DateTime.ParseExact(
contentString.Substring(3, Math.Min(18, contentString.Length - 3)),
formats,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None);
// 提取数据部分 / Extract data part
int dataStartIdx = contentString.IndexOf("&&") + 2;
string head = contentString.Substring(0, dataStartIdx).Substring(21);
string[] contentUnitAll = contentString.Substring(dataStartIdx).Split(';')[1].Split(',');
System.Collections.Generic.List<string> contentSplitAll = new System.Collections.Generic.List<string>();
string newContent = "DataTime=" + startTime.ToString("yyyyMMddHHmmss") + ";";
// 分包处理 / Packet splitting
foreach (var contentUnit in contentUnitAll)
{
if (newContent.Length + contentUnit.Length > 1000 - dataStartIdx)
{
// 生成新包 / Generate new packet
contentSplitAll.Add("QN=" + startTime.ToString("yyyyMMddHHmmssffff") + head + newContent[..^1]);
startTime = startTime.AddSeconds(1); // 时间戳递增 / Increment timestamp
newContent = "DataTime=" + startTime.ToString("yyyyMMddHHmmss") + ";" + contentUnit + ",";
}
else
{
newContent += contentUnit + ",";
}
}
contentSplitAll.Add("QN=" + startTime.ToString("yyyyMMddHHmmssffff") + head + newContent[..^1]);
// 发送所有包并合并响应 / Send all packets and merge responses
var receiveBytesAll = new System.Collections.Generic.List<byte>();
foreach (var contentSplit in contentSplitAll)
{
var extBytes = BytesExtend(Encoding.ASCII.GetBytes(contentSplit));
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
receiveBytesAll.AddRange(receiveBytes == null ? null : receiveBytes.Length == 0 ? receiveBytes : BytesDecact(receiveBytes));
}
return receiveBytesAll.ToArray();
}
}
/// <summary>
/// 检查接收数据 / Check Received Data
/// <remarks>
/// HJ212 协议特性:总是返回正确
/// HJ212 protocol characteristic: always returns correct
/// <para>
/// 原因 / Reason:
/// <list type="bullet">
/// <item>HJ212 协议由平台端校验 / HJ212 protocol is validated by platform side</item>
/// <item>设备端只需发送数据 / Device side only needs to send data</item>
/// <item>响应由平台处理 / Response is handled by platform</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="content">
/// 设备返回的数据 / Data Returned from Device
/// <remarks>
/// HJ212 协议响应
/// HJ212 protocol response
/// </remarks>
/// </param>
/// <returns>
/// 数据是否正确 / Whether Data is Correct
/// <remarks>
/// 总是返回 true
/// Always returns true
/// </remarks>
/// </returns>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
return true; return true;

View File

@@ -1,42 +1,308 @@
using System; using System;
using System.Security.Cryptography;
using System.Text; using System.Text;
namespace Modbus.Net.HJ212 namespace Modbus.Net.HJ212
{ {
/// <summary> /// <summary>
/// Rtu协议字节伸缩 /// HJ212 协议字节扩展类 / HJ212 Protocol Bytes Extend Class
/// <remarks>
/// 实现 HJ212-2017 环保协议的字节扩展和收缩功能
/// Implements bytes extend and reduce functionality for HJ212-2017 environmental protection protocol
/// <para>
/// 协议格式 / Protocol Format:
/// <code>##FLdata&&CRC##</code>
/// <list type="bullet">
/// <item><strong>##</strong> - 起始符 / Start marker</item>
/// <item><strong>FL</strong> - 长度标识 (4 位数字) / Length identifier (4 digits)</item>
/// <item><strong>data</strong> - 数据内容 / Data content</item>
/// <item><strong>&&CRC</strong> - CRC32 校验 (4 位 16 进制) / CRC32 checksum (4 hex digits)</item>
/// <item><strong>##</strong> - 结束符 / End marker</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var bytesExtend = new HJ212ProtocolLinkerBytesExtend();
///
/// // 扩展 (发送前) / Extend (before sending)
/// byte[] rawData = Encoding.ASCII.GetBytes("QN=20250327100000;ST=32;CN=1011;...");
/// byte[] extendedData = bytesExtend.BytesExtend(rawData);
/// // 结果:##0123QN=20250327100000;ST=32;CN=1011;...&&ABCD##
/// ///
/// // 收缩 (接收后) / Reduce (after receiving)
/// byte[] receivedData = Encoding.ASCII.GetBytes("##0123data&&ABCD##");
/// byte[] reducedData = bytesExtend.BytesDecact(receivedData);
/// // 结果data (移除头部和尾部)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class HJ212ProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]> public class HJ212ProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
{ {
/// <summary> /// <summary>
/// 协议扩展,协议内容发送前调用 /// 协议扩展 (发送前调用) / Protocol Extend (Called Before Sending)
/// <remarks>
/// 在 HJ212 协议数据前添加包头,后添加 CRC 校验和结束符
/// Add packet header before HJ212 protocol data, add CRC checksum and terminator after
/// <para>
/// 添加的头部 / Added Header (6 bytes):
/// <list type="bullet">
/// <item><strong>##</strong> - 起始符 (2 字节) / Start marker (2 bytes)</item>
/// <item><strong>FL</strong> - 总长度 (4 位数字,包含##和&&CRC) / Total length (4 digits, includes ## and &&CRC)</item>
/// </list>
/// </para>
/// <para>
/// 添加的尾部 / Added Tail (6 bytes):
/// <list type="bullet">
/// <item><strong>&&</strong> - CRC 标识 (2 字节) / CRC identifier (2 bytes)</item>
/// <item><strong>CRC</strong> - CRC32 校验 (4 位 16 进制) / CRC32 checksum (4 hex digits)</item>
/// <item><strong>##</strong> - 结束符 (2 字节) / End marker (2 bytes)</item>
/// </list>
/// </para>
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>计算总长度 (内容 +10 字节) / Calculate total length (content +10 bytes)</item>
/// <item>构建头部 (##+ 长度) / Build header (##+length)</item>
/// <item>计算 CRC32 / Calculate CRC32</item>
/// <item>构建尾部 (&&CRC+##) / Build tail (&&CRC+##)</item>
/// <item>组合:头部 + 内容 + 尾部 / Combine: header + content + tail</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">扩展前的原始协议内容</param> /// <param name="content">
/// <returns>扩展后的协议内容</returns> /// 扩展前的原始协议内容 / Original Protocol Content Before Extension
/// <remarks>
/// ASCII 编码的 HJ212 协议数据
/// ASCII-encoded HJ212 protocol data
/// </remarks>
/// </param>
/// <returns>
/// 扩展后的协议内容 / Extended Protocol Content
/// <remarks>
/// 添加了包头和 CRC 校验的完整 HJ212 帧
/// Complete HJ212 frame with packet header and CRC checksum added
/// </remarks>
/// </returns>
public byte[] BytesExtend(byte[] content) public byte[] BytesExtend(byte[] content)
{ {
var crc = new byte[2]; // 计算数据段长度 (包含##和&&CRC)
//Modbus/Rtu协议扩张增加CRC校验 // Calculate data segment length (includes ## and &&CRC)
var newFormat = new byte[content.Length + 4]; int totalLength = content.Length + 10; // 6(##FL) + 4(CRC)
Crc16.GetInstance().GetCRC(content, ref crc); string lengthString = totalLength.ToString("0000");
Array.Copy(content, 0, newFormat, 0, content.Length);
string crcString = BitConverter.ToString(crc).Replace("-", string.Empty); // 构建头部:## + 长度 / Build header: ## + length
var crcCalc = Encoding.ASCII.GetBytes(crcString); byte[] header = Encoding.ASCII.GetBytes("##" + lengthString);
Array.Copy(crcCalc, 0, newFormat, newFormat.Length - 4, 4);
return newFormat; // 计算 CRC32 / Calculate CRC32
string crc = HJ212CRC32.Calculate(Encoding.ASCII.GetString(content));
byte[] crcBytes = Encoding.ASCII.GetBytes("&&" + crc + "##");
// 组合:头部 + 内容 + CRC+ 结束符
// Combine: header + content + CRC+ terminator
byte[] result = new byte[header.Length + content.Length + crcBytes.Length];
Array.Copy(header, 0, result, 0, header.Length);
Array.Copy(content, 0, result, header.Length, content.Length);
Array.Copy(crcBytes, 0, result, header.Length + content.Length, crcBytes.Length);
return result;
} }
/// <summary> /// <summary>
/// 协议收缩,协议内容接收后调用 /// 协议收缩 (接收后调用) / Protocol Reduce (Called After Receiving)
/// <remarks>
/// 移除 HJ212 协议数据的包头和尾部
/// Remove packet header and tail from HJ212 protocol data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>检查最小包长度 (16 字节) / Check minimum packet length (16 bytes)</item>
/// <item>跳过 6 字节头部 (##FL) / Skip 6-byte header (##FL)</item>
/// <item>跳过 6 字节尾部 (&&CRC##) / Skip 6-byte tail (&&CRC##)</item>
/// <item>提取中间的数据内容 / Extract middle data content</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">收缩前的完整协议内容</param> /// <param name="content">
/// <returns>收缩后的协议内容</returns> /// 收缩前的完整协议内容 / Complete Protocol Content Before Reduction
/// <remarks>
/// 包含包头和 CRC 校验的完整 HJ212 帧
/// Complete HJ212 frame with packet header and CRC checksum
/// </remarks>
/// </param>
/// <returns>
/// 收缩后的协议内容 / Reduced Protocol Content
/// <remarks>
/// 移除包头和尾部后的 HJ212 协议数据
/// HJ212 protocol data with packet header and tail removed
/// </remarks>
/// </returns>
public byte[] BytesDecact(byte[] content) public byte[] BytesDecact(byte[] content)
{ {
//Modbus/Rtu协议收缩抛弃后面2个字节的内容 // 移除头部 (##FL) 和尾部 (&&CRC##)
var newContent = new byte[content.Length - 2]; // Remove header (##FL) and tail (&&CRC##)
Array.Copy(content, 0, newContent, 0, newContent.Length);
// 最小包长度检查 / Minimum packet length check
if (content.Length < 16) // 最小包长度 / Minimum packet length
return content;
// 提取数据段 (跳过 6 字节头部和 6 字节尾部)
// Extract data segment (skip 6-byte header and 6-byte tail)
byte[] newContent = new byte[content.Length - 12];
Array.Copy(content, 6, newContent, 0, newContent.Length);
return newContent; return newContent;
} }
} }
#region HJ212 CRC32 / HJ212 CRC32 Checksum Utility
/// <summary>
/// HJ212 CRC32 校验工具类 / HJ212 CRC32 Checksum Utility Class
/// <remarks>
/// 实现 HJ212-2017 协议专用的 CRC32 校验算法
/// Implements CRC32 checksum algorithm specific to HJ212-2017 protocol
/// <para>
/// 算法特点 / Algorithm Characteristics:
/// <list type="bullet">
/// <item>标准 CRC32 多项式0xEDB88320 / Standard CRC32 polynomial: 0xEDB88320</item>
/// <item>初始值0xFFFFFFFF / Initial value: 0xFFFFFFFF</item>
/// <item>结果异或0xFFFFFFFF / Result XOR: 0xFFFFFFFF</item>
/// <item>返回 4 位 16 进制字符串 (截断高 16 位) / Returns 4 hex digit string (truncated high 16 bits)</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 计算 CRC32 / Calculate CRC32
/// string data = "QN=20250327100000;ST=32;CN=1011;...";
/// string crc = HJ212CRC32.Calculate(data);
/// // 结果:"ABCD" (4 位 16 进制)
/// ///
/// // 验证 CRC32 / Verify CRC32
/// bool isValid = HJ212CRC32.Verify(data, "ABCD");
/// // 结果true 或 false
/// </code>
/// </para>
/// </remarks>
/// </summary>
public static class HJ212CRC32
{
/// <summary>
/// CRC32 查找表 / CRC32 Lookup Table
/// <remarks>
/// 预先计算的 CRC32 值,用于快速计算
/// Pre-calculated CRC32 values for fast calculation
/// </remarks>
/// </summary>
private static readonly uint[] CrcTable = GenerateCrcTable();
/// <summary>
/// 生成 CRC32 查找表 / Generate CRC32 Lookup Table
/// <remarks>
/// 使用标准 CRC32 多项式 0xEDB88320 生成 256 项查找表
/// Generate 256-entry lookup table using standard CRC32 polynomial 0xEDB88320
/// </remarks>
/// </summary>
/// <returns>CRC32 查找表 / CRC32 Lookup Table</returns>
private static uint[] GenerateCrcTable()
{
var table = new uint[256];
for (uint i = 0; i < 256; i++)
{
uint crc = i;
for (int j = 0; j < 8; j++)
{
crc = (crc & 1) == 1 ? (crc >> 1) ^ 0xEDB88320 : crc >> 1;
}
table[i] = crc;
}
return table;
}
/// <summary>
/// 计算 CRC32 / Calculate CRC32
/// <remarks>
/// 计算字符串数据的 CRC32 校验值,返回 4 位 16 进制字符串
/// Calculate CRC32 checksum for string data, return 4 hex digit string
/// <para>
/// 计算流程 / Calculation Flow:
/// <list type="number">
/// <item>字符串转 ASCII 字节 / Convert string to ASCII bytes</item>
/// <item>初始化 CRC=0xFFFFFFFF / Initialize CRC=0xFFFFFFFF</item>
/// <item>逐字节查表计算 / Calculate byte-by-byte using lookup table</item>
/// <item>结果异或 0xFFFFFFFF / XOR result with 0xFFFFFFFF</item>
/// <item>取高 16 位,转为 4 位 16 进制 / Take high 16 bits, convert to 4 hex digits</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="data">
/// 待计算的数据 / Data to Calculate
/// <remarks>
/// ASCII 编码的字符串
/// ASCII-encoded string
/// </remarks>
/// </param>
/// <returns>
/// CRC32 校验值 (4 位 16 进制字符串) / CRC32 Checksum (4 hex digit string)
/// <remarks>
/// 例如:"ABCD", "1234" 等
/// e.g., "ABCD", "1234", etc.
/// </remarks>
/// </returns>
public static string Calculate(string data)
{
byte[] bytes = Encoding.ASCII.GetBytes(data);
uint crc = 0xFFFFFFFF;
foreach (byte b in bytes)
{
crc = (crc >> 8) ^ CrcTable[(crc ^ b) & 0xFF];
}
crc ^= 0xFFFFFFFF;
return crc.ToString("X8").Substring(0, 4); // HJ212 使用 4 位 CRC / HJ212 uses 4-bit CRC
}
/// <summary>
/// 验证 CRC32 / Verify CRC32
/// <remarks>
/// 验证数据的 CRC32 校验值是否正确
/// Verify if CRC32 checksum for data is correct
/// <para>
/// 验证流程 / Verification Flow:
/// <list type="number">
/// <item>重新计算数据的 CRC32 / Recalculate CRC32 for data</item>
/// <item>与提供的 CRC 比较 (不区分大小写) / Compare with provided CRC (case-insensitive)</item>
/// <item>返回比较结果 / Return comparison result</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="data">
/// 待验证的数据 / Data to Verify
/// <remarks>ASCII 编码的字符串 / ASCII-encoded string</remarks>
/// </param>
/// <param name="crc">
/// 提供的 CRC 校验值 / Provided CRC Checksum
/// <remarks>4 位 16 进制字符串 / 4 hex digit string</remarks>
/// </param>
/// <returns>
/// 验证结果 / Verification Result
/// <remarks>
/// true: CRC 正确 / CRC correct
/// false: CRC 错误 / CRC error
/// </remarks>
/// </returns>
public static bool Verify(string data, string crc)
{
string calculated = Calculate(data);
return string.Equals(calculated, crc, StringComparison.OrdinalIgnoreCase);
}
}
#endregion
} }

View File

@@ -1,53 +1,301 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.HJ212 namespace Modbus.Net.HJ212
{ {
/// <summary>
/// HJ212 协议工具类 / HJ212 Protocol Utility Class
/// <remarks>
/// 实现环保行业 HJ212-2017 协议,用于污染源在线监测设备通信
/// Implements HJ212-2017 protocol for environmental protection industry, used for pollution source online monitoring equipment communication
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 TCP/IP 传输 / Based on TCP/IP transport</item>
/// <item>ASCII 字符编码 / ASCII character encoding</item>
/// <item>命令 - 响应模式 / Command-response mode</item>
/// <item>支持数据上报、查询、控制等功能 / Supports data reporting, query, control, etc.</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [起始符 (1)][数据长度 (4)][命令字段 (5)][数据内容 (N)][CRC (4)][结束符 (2)]
/// │ │ │ │ │ │
/// └─ 0x3A └─ 十六进制 └─ 命令类型 └─ 实际数据 └─ CRC 校验 └─ 0x0D 0x0A
/// </code>
/// </para>
/// <para>
/// 主要功能码 / Main Function Codes:
/// <list type="bullet">
/// <item><strong>2011</strong> - 实时数据查询 / Real-time data query</item>
/// <item><strong>2041</strong> - 历史数据查询 / Historical data query</item>
/// <item><strong>2051</strong> - 设备时间查询 / Device time query</item>
/// <item><strong>2061</strong> - 设备时间设置 / Device time set</item>
/// <item><strong>3011</strong> - 实时数据上报 / Real-time data report</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 HJ212 工具实例 / Create HJ212 utility instance
/// var utility = new HJ212Utility("192.168.1.100:8080");
///
/// // 连接设备 / Connect to device
/// await utility.ConnectAsync();
///
/// // 查询实时数据 / Query real-time data
/// var result = await utility.GetDatasAsync(
/// "ST=32;CN=2011;StartTime=20250327100000;EndTime=20250327110000",
/// 0, 0
/// );
///
/// if (result.IsSuccess)
/// {
/// string data = System.Text.Encoding.ASCII.GetString(result.Datas);
/// Console.WriteLine($"实时数据:{data}");
/// }
///
/// // 写入数据 (控制命令) / Write data (control command)
/// await utility.SetDatasAsync(
/// "ST=32;CN=2061",
/// new object[] { "Password", "DeviceID", "Command", "Data", dataList, DateTime.Now }
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class HJ212Utility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit> public class HJ212Utility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit>
{ {
private static readonly ILogger<HJ212Utility> logger = LogProvider.CreateLogger<HJ212Utility>(); private static readonly ILogger<HJ212Utility> logger = LogProvider.CreateLogger<HJ212Utility>();
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化 HJ212 协议工具类
/// Initialize HJ212 protocol utility class
/// </remarks>
/// </summary>
/// <param name="connectionString">
/// 连接字符串 / Connection String
/// <remarks>
/// 格式:"IP:Port",如 "192.168.1.100:8080"
/// Format: "IP:Port", e.g., "192.168.1.100:8080"
/// </remarks>
/// </param>
public HJ212Utility(string connectionString) : base(0, 0) public HJ212Utility(string connectionString) : base(0, 0)
{ {
ConnectionString = connectionString; ConnectionString = connectionString;
// 创建 HJ212 协议实例 / Create HJ212 protocol instance
Wrapper = new HJ212Protocol(connectionString); Wrapper = new HJ212Protocol(connectionString);
} }
public override Endian Endian => throw new NotImplementedException(); /// <summary>
/// 端格式 / Endianness
/// <remarks>
/// HJ212 协议使用大端格式
/// HJ212 protocol uses Big Endian format
/// </remarks>
/// </summary>
public override Endian Endian => Endian.BigEndianLsb;
public override Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount) /// <summary>
{ /// 读取数据 / Read Data
throw new NotImplementedException(); /// <remarks>
} /// 实现 BaseUtility 的抽象方法,读取 HJ212 设备数据
/// Implements abstract method from BaseUtility, reads HJ212 device data
public override void SetConnectionType(int connectionType) /// <para>
{ /// 地址格式 / Address Format:
throw new NotImplementedException(); /// <code>ST=32;CN=2011;StartTime=20250327100000;EndTime=20250327110000</code>
} /// <list type="bullet">
/// <item><strong>ST</strong> - 污染物类型 / Pollutant type</item>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents) /// <item><strong>CN</strong> - 命令编号 / Command number</item>
/// <item><strong>StartTime</strong> - 开始时间 / Start time</item>
/// <item><strong>EndTime</strong> - 结束时间 / End time</item>
/// </list>
/// </para>
/// <para>
/// 常用命令 / Common Commands:
/// <list type="bullet">
/// <item><strong>CN=2011</strong> - 实时数据查询 / Real-time data query</item>
/// <item><strong>CN=2041</strong> - 历史数据查询 / Historical data query</item>
/// <item><strong>CN=2051</strong> - 设备时间查询 / Device time query</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="startAddress">
/// 开始地址 (命令参数) / Start Address (Command Parameters)
/// <remarks>
/// 格式:"ST=32;CN=2011;StartTime=...;EndTime=..."
/// Format: "ST=32;CN=2011;StartTime=...;EndTime=..."
/// </remarks>
/// </param>
/// <param name="getByteCount">获取字节数个数 / Number of Bytes to Get</param>
/// <param name="getOriginalCount">获取原始个数 / Get Original Count</param>
/// <returns>
/// 接收到的 byte 数据 / Received Byte Data
/// <remarks>
/// ReturnStruct&lt;byte[]&gt; 包含:
/// ReturnStruct&lt;byte[]&gt; contains:
/// <list type="bullet">
/// <item>Datas: ASCII 编码的响应数据 / ASCII-encoded response data</item>
/// <item>IsSuccess: 读取是否成功 / Read success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount, int getOriginalCount)
{ {
try try
{ {
var writeRequestHJ212InputStruct = // 解析地址格式 / Parse address format
new WriteRequestHJ212InputStruct((string)setContents[0], (string)setContents[1], (string)setContents[2], (string)setContents[3], (List<Dictionary<string, string>>)setContents[4], (DateTime)setContents[5]); // "ST=32;CN=2011;StartTime=20250327100000;EndTime=20250327110000"
var writeRequestOpcOutputStruct = var parts = startAddress.Split(';');
await string st = "32", cn = "2011", startTime = "", endTime = "";
Wrapper.SendReceiveAsync<WriteRequestHJ212OutputStruct>(Wrapper[typeof(WriteRequestHJ212Protocol)],
writeRequestHJ212InputStruct); foreach (var part in parts)
return new ReturnStruct<bool>
{ {
Datas = writeRequestOpcOutputStruct?.GetValue != null, if (part.StartsWith("ST=")) st = part.Substring(3);
IsSuccess = writeRequestOpcOutputStruct?.GetValue != null, else if (part.StartsWith("CN=")) cn = part.Substring(3);
else if (part.StartsWith("StartTime=")) startTime = part.Substring(10);
else if (part.StartsWith("EndTime=")) endTime = part.Substring(8);
}
// 检查必需参数 / Check required parameters
if (string.IsNullOrEmpty(startTime) || string.IsNullOrEmpty(endTime))
{
return new ReturnStruct<byte[]>
{
Datas = null,
IsSuccess = false,
ErrorCode = -1,
ErrorMsg = "StartTime and EndTime are required"
};
}
// 创建读取请求输入结构 / Create read request input structure
var readRequest = new ReadRequestHJ212InputStruct(
st, // 污染物类型 / Pollutant type
cn, // 命令编号 / Command number
"123456", // 密码 / Password
"888888888888888001", // 设备标识 / Device ID
DateTime.ParseExact(startTime, "yyyyMMddHHmmss", null), // 开始时间
DateTime.ParseExact(endTime, "yyyyMMddHHmmss", null) // 结束时间
);
// 发送接收 / Send and receive
var outputStruct = await Wrapper.SendReceiveAsync<ReadRequestHJ212OutputStruct>(
Wrapper[typeof(ReadRequestHJ212Protocol)], readRequest);
// 返回 ASCII 编码的数据 / Return ASCII-encoded data
return new ReturnStruct<byte[]>
{
Datas = System.Text.Encoding.ASCII.GetBytes(outputStruct?.GetValue ?? ""),
IsSuccess = !string.IsNullOrEmpty(outputStruct?.GetValue),
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null, ErrorMsg = null
}; };
} }
catch (Exception e) catch (Exception e)
{ {
logger.LogError(e, $"OpcUtility -> SetDatas: {ConnectionString} error: {e.Message}"); logger.LogError(e, $"HJ212Utility -> GetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<byte[]>
{
Datas = null,
IsSuccess = false,
ErrorCode = -100,
ErrorMsg = e.Message
};
}
}
/// <summary>
/// 写入数据 / Write Data
/// <remarks>
/// 实现 BaseUtility 的抽象方法,写入 HJ212 设备数据
/// Implements abstract method from BaseUtility, writes HJ212 device data
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>设备时间设置 / Device time set</item>
/// <item>设备控制命令 / Device control command</item>
/// <item>参数设置 / Parameter set</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="startAddress">
/// 开始地址 (命令参数) / Start Address (Command Parameters)
/// <remarks>
/// 格式:"ST=32;CN=2061" (时间设置)
/// Format: "ST=32;CN=2061" (time set)
/// </remarks>
/// </param>
/// <param name="setContents">
/// 设置数据 / Set Data
/// <remarks>
/// 对象数组,包含命令参数
/// Object array containing command parameters
/// <para>
/// 通常包含 / Usually contains:
/// <list type="bullet">
/// <item>Password (string) - 密码</item>
/// <item>DeviceID (string) - 设备标识</item>
/// <item>Command (string) - 命令</item>
/// <item>Data (string) - 数据</item>
/// <item>DataList (List&lt;Dictionary&gt;) - 数据列表</item>
/// <item>DateTime - 时间戳</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="setOriginalCount">设置原始长度 / Set Original Length</param>
/// <returns>
/// 是否设置成功 / Whether Set is Successful
/// <remarks>
/// ReturnStruct&lt;bool&gt;:
/// <list type="bullet">
/// <item>Datas: true=成功false=失败 / true=success, false=failure</item>
/// <item>IsSuccess: 操作是否成功 / Operation success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents, int setOriginalCount)
{
try
{
// 创建写入请求输入结构 / Create write request input structure
var writeRequestHJ212InputStruct = new WriteRequestHJ212InputStruct(
(string)setContents[0], // Password
(string)setContents[1], // DeviceID
(string)setContents[2], // Command
(string)setContents[3], // Data
(List<Dictionary<string, string>>)setContents[4], // DataList
(DateTime)setContents[5] // DateTime
);
// 发送接收 / Send and receive
var writeRequestHJ212OutputStruct = await Wrapper.SendReceiveAsync<WriteRequestHJ212OutputStruct>(
Wrapper[typeof(WriteRequestHJ212Protocol)], writeRequestHJ212InputStruct);
// 返回结果 / Return result
return new ReturnStruct<bool>
{
Datas = writeRequestHJ212OutputStruct?.GetValue != null,
IsSuccess = writeRequestHJ212OutputStruct?.GetValue != null,
ErrorCode = 0,
ErrorMsg = null
};
}
catch (Exception e)
{
logger.LogError(e, $"HJ212Utility -> SetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<bool> return new ReturnStruct<bool>
{ {
Datas = false, Datas = false,

View File

@@ -1,7 +1,233 @@
Modbus.Net.HJ212 # Modbus.Net.HJ212
===================
[![NuGet](https://img.shields.io/nuget/v/Modbus.Net.HJ212.svg)](https://www.nuget.org/packages/Modbus.Net.HJ212/) [![NuGet](https://img.shields.io/nuget/v/Modbus.Net.HJ212.svg)](https://www.nuget.org/packages/Modbus.Net.HJ212/)
HJ212 Implementation of Modbus.Net ## HJ212 协议完整实现
Doc has been moved to wiki. 基于 **HJ 212-2025** 标准的污染物在线监控 (监测) 系统数据传输协议实现。
## 功能特性
### ✅ 已实现
| 功能 | 命令码 | 状态 |
|------|--------|------|
| 系统登录 | CN=1001 | ✅ |
| 系统登出 | CN=1002 | ✅ |
| 心跳 | CN=1005 | ✅ |
| 实时数据上传 | CN=1011 | ✅ |
| 分钟数据上传 | CN=1012 | ✅ |
| 小时数据上传 | CN=1013 | ✅ |
| 日数据上传 | CN=1014 | ✅ |
| 数据查询 | CN=2011 | ✅ |
| 设备校时 | CN=2041 | ⚠️ 框架 |
| 参数查询 | CN=2051 | ⚠️ 框架 |
| 参数设置 | CN=2052 | ⚠️ 框架 |
| 设备控制 | CN=2061 | ⚠️ 框架 |
| 报警信息 | CN=3011 | ✅ |
| 状态信息 | CN=3021 | ✅ |
### 📊 因子编码支持
#### 水污染物 (ST=32)
- `w01011` COD (化学需氧量)
- `w01012` 氨氮 (NH3-N)
- `w01013` 总磷 (TP)
- `w01014` 总氮 (TN)
- `w01015` pH 值
- `w01016` 溶解氧 (DO)
- `w01017` 浊度
- `w01018` 电导率
- `w01019` 流量
- `w01020` 水温
- ... (共 37 个因子)
#### 气污染物 (ST=33)
- `g01011` SO2 (二氧化硫)
- `g01012` NOx (氮氧化物)
- `g01013` PM10
- `g01014` PM2.5
- `g01015` CO (一氧化碳)
- `g01016` O3 (臭氧)
- `g01022` VOCs (挥发性有机物)
- `g01026` 烟气温度
- `g01030` 含氧量
- `g01031` 烟气流量
- ... (共 40 个因子)
## 快速开始
### 1. 使用 Utility (低级 API)
```csharp
using Modbus.Net.HJ212;
// 创建 Utility
var utility = new HJ212Utility("192.168.1.100:9002");
// 连接
await utility.ConnectAsync();
// 系统登录
await utility.LoginAsync("32", "123456", "888888888888888001");
// 上传实时数据
var dataRecords = new List<Dictionary<string, string>>
{
new Dictionary<string, string>
{
{ "w01011-Rtd", "25.6" }, // COD
{ "w01011-Flag", "N" },
{ "w01012-Rtd", "1.23" }, // 氨氮
{ "w01012-Flag", "N" }
}
};
await utility.UploadRealtimeDataAsync(
"32", // ST: 水污染
"123456", // PW: 密码
"888888888888888001", // MN: 设备编号
dataRecords,
DateTime.Now
);
// 心跳
await utility.HeartbeatAsync("32", "123456", "888888888888888001");
```
### 2. 使用 Machine (中级 API)
```csharp
using Modbus.Net.HJ212;
// 创建 Machine
var machine = new HJ212Machine<string, string>(
"Device001", // ID
"水质监测站", // 别名
"192.168.1.100:9002", // 连接字符串
"32", // ST: 水污染
"123456", // PW: 密码
"888888888888888001" // MN: 设备编号
);
// 登录
await machine.LoginAsync();
// 上传数据
var factors = new Dictionary<string, double>
{
{ WaterPollutantFactors.COD, 25.6 },
{ WaterPollutantFactors.NH3N, 1.23 },
{ WaterPollutantFactors.PH, 7.5 }
};
await machine.UploadRealtimeDataAsync(factors);
// 上传小时数据
await machine.UploadHourDataAsync(DateTime.Now.AddHours(-1), factors);
// 上传日数据
await machine.UploadDayDataAsync(DateTime.Now.AddDays(-1), factors);
```
### 3. 数据包解析
```csharp
// 解析接收到的数据包
string packetStr = "##0156QN=20250327102200001234;ST=32;CN=1011;PW=123456;MN=888888888888888001;CP=&&DataTime=20250327102200;w01011-Rtd=25.6,w01011-Flag=N&&A1B2C3D4##";
// 使用 CRC32 验证
string dataSegment = packetStr.Substring(6, packetStr.Length - 16);
string receivedCrc = packetStr.Substring(packetStr.Length - 10, 4);
bool isValid = HJ212CRC32.Verify(dataSegment, receivedCrc);
```
## 数据包格式
```
##FLdata&&CRC##
FL: 数据长度 (4 位十进制,包含##和&&CRC##)
data: 数据段 (QN=...;ST=...;CN=...;...)
CRC: CRC32 校验 (4 位 16 进制)
```
### 示例
```
##0156QN=20250327102200001234;ST=32;CN=1011;PW=123456;MN=888888888888888001;CP=&&DataTime=20250327102200;w01011-Rtd=25.6,w01011-Flag=N&&A1B2C3D4##
```
## 数据标志 (Flag)
| 标志 | 说明 |
|------|------|
| N | 正常 |
| S | 超标 |
| M | 维护 |
| C | 校准 |
| E | 错误 |
| L | 低于检出限 |
| D | 缺失 |
## 配置说明
### 连接字符串
```
IP:Port
示例192.168.1.100:9002
```
### appsettings.json
```json
{
"Modbus.Net:TCP:192.168.1.100:9002:HJ212Port": "9002",
"Modbus.Net:TCP:192.168.1.100:9002:FetchSleepTime": "1000"
}
```
## 与 Modbus.Net 集成
Modbus.Net.HJ212 是 Modbus.Net 框架的扩展,遵循相同的架构模式:
- **Protocol**: `HJ212Protocol`
- **ProtocolLinker**: `HJ212ProtocolLinker`
- **Utility**: `HJ212Utility`
- **Machine**: `HJ212Machine<TKey, TUnitKey>`
- **Controller**: `HJ212Controller`
## 依赖
- .NET 6.0+
- Modbus.Net (核心框架)
- Microsoft.Extensions.Logging
## 参考文档
- HJ 212-2025 污染物在线监控 (监测) 系统数据传输标准
- [HJ212-2025-Protocol.md](../../../.openclaw/workspace-open-coder/HJ212-2025-Protocol.md) - 详细协议文档
## 更新历史
### v2.0.0 (2026-03-27)
- ✅ 实现 `GetDatasAsync` 方法
- ✅ 添加系统登录/登出功能
- ✅ 添加心跳功能
- ✅ 添加完整的因子编码定义
- ✅ 修正 CRC32 校验算法
- ✅ 添加数据查询功能
- ✅ 完善文档和示例
### v1.0.0 (2019)
- 初始版本
- 基础写数据功能
## 许可证
MIT License
## 贡献
欢迎提交 Issue 和 Pull Request

View File

@@ -1,28 +1,159 @@
namespace Modbus.Net.Modbus.NA200H namespace Modbus.Net.Modbus.NA200H
{ {
/// <summary> /// <summary>
/// 南大奥拓NA200H专用AddressFormater /// 南大奥拓 NA200H 专用地址格式化器 / Nanda Aotuo NA200H Dedicated Address Formater
/// <remarks>
/// 实现 NA200H 专用协议的地址格式化功能
/// Implements address formatting functionality for NA200H dedicated protocol
/// <para>
/// NA200H 协议特点 / NA200H Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 Modbus 协议的扩展 / Extension based on Modbus protocol</item>
/// <item>支持多种数据区域 (Q/M/N/I/S 等) / Supports multiple data areas (Q/M/N/I/S, etc.)</item>
/// <item>地址映射到标准 Modbus 地址 / Maps addresses to standard Modbus addresses</item>
/// </list>
/// </para>
/// <para>
/// 地址格式 / Address Format:
/// <list type="bullet">
/// <item><strong>无子地址</strong>: "Area Address" (如 "Q 1") / Without sub-address: "Area Address" (e.g., "Q 1")</item>
/// <item><strong>有子地址</strong>: "Area Address.SubAddress" (如 "Q 1.3") / With sub-address: "Area Address.SubAddress" (e.g., "Q 1.3")</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var formater = new AddressFormaterNA200H();
///
/// // 格式化地址 (无子地址) / Format address (without sub-address)
/// string addr1 = formater.FormatAddress("Q", 1);
/// // 结果:"Q 1"
///
/// // 格式化地址 (有子地址) / Format address (with sub-address)
/// string addr2 = formater.FormatAddress("MW", 100, 3);
/// // 结果:"MW 100.3"
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressFormaterNA200H : AddressFormater<int, int> public class AddressFormaterNA200H : AddressFormater<int, int>
{ {
/// <summary> /// <summary>
/// 格式化地址 /// 格式化地址 (无子地址) / Format Address (without Sub-Address)
/// <remarks>
/// 将区域和地址转换为 NA200H 标准格式字符串
/// Convert area and address to NA200H standard format string
/// <para>
/// 格式 / Format:
/// <code>Area + " " + Address</code>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>Area="Q", Address=1 → "Q 1"</item>
/// <item>Area="MW", Address=100 → "MW 100"</item>
/// <item>Area="IW", Address=50 → "IW 50"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址区域</param> /// <param name="area">
/// <param name="address">地址</param> /// 地址区域 / Address Area
/// <returns>格式化的地址字符串</returns> /// <remarks>
/// NA200H 区域标识
/// NA200H area identifier
/// <para>
/// 有效值 / Valid Values:
/// <list type="bullet">
/// <item>"Q" - 输出继电器 / Output relay</item>
/// <item>"M" - 辅助继电器 / Auxiliary relay</item>
/// <item>"N" - 特殊继电器 / Special relay</item>
/// <item>"I" - 输入继电器 / Input relay</item>
/// <item>"S" - 状态继电器 / Status relay</item>
/// <item>"IW" - 输入寄存器 / Input register</item>
/// <item>"SW" - 特殊寄存器 / Special register</item>
/// <item>"MW" - 辅助寄存器 / Auxiliary register</item>
/// <item>"QW" - 输出寄存器 / Output register</item>
/// <item>"NW" - 特殊寄存器 / Special register</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="address">
/// 地址 / Address
/// <remarks>
/// 地址偏移量 (从 1 开始)
/// Address offset (starts from 1)
/// </remarks>
/// </param>
/// <returns>
/// 格式化的地址字符串 / Formatted Address String
/// <remarks>
/// 格式:"Area Address"
/// Format: "Area Address"
/// </remarks>
/// </returns>
public override string FormatAddress(string area, int address) public override string FormatAddress(string area, int address)
{ {
return area + " " + address; return area + " " + address;
} }
/// <summary> /// <summary>
/// 格式化地址 /// 格式化地址 (带子地址) / Format Address (with Sub-Address)
/// <remarks>
/// 将区域、地址和子地址转换为 NA200H 标准格式字符串
/// Convert area, address and sub-address to NA200H standard format string
/// <para>
/// 格式 / Format:
/// <code>Area + " " + Address + "." + SubAddress</code>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>Area="Q", Address=1, SubAddress=0 → "Q 1.0"</item>
/// <item>Area="M", Address=100, SubAddress=3 → "M 100.3"</item>
/// <item>Area="MW", Address=50, SubAddress=7 → "MW 50.7"</item>
/// </list>
/// </para>
/// <para>
/// 子地址说明 / Sub-Address Description:
/// <list type="bullet">
/// <item>范围0-7 (一个字节的 8 个位) / Range: 0-7 (8 bits of one byte)</item>
/// <item>用于访问单个位 / Used to access individual bits</item>
/// <item>仅对 Boolean 类型有效 / Only valid for Boolean type</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址区域</param> /// <param name="area">
/// <param name="address">地址</param> /// 地址区域 / Address Area
/// <param name="subAddress">比特位地址</param> /// <remarks>NA200H 区域标识 / NA200H area identifier</remarks>
/// <returns>格式化的地址字符串</returns> /// </param>
/// <param name="address">
/// 地址 / Address
/// <remarks>地址偏移量 / Address offset</remarks>
/// </param>
/// <param name="subAddress">
/// 比特位地址 / Bit Address
/// <remarks>
/// 子地址 (位偏移)
/// Sub-address (bit offset)
/// <para>
/// 范围 / Range: 0-7
/// <list type="bullet">
/// <item>0 - 最低位 / LSB</item>
/// <item>7 - 最高位 / MSB</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 格式化的地址字符串 / Formatted Address String
/// <remarks>
/// 格式:"Area Address.SubAddress"
/// Format: "Area Address.SubAddress"
/// </remarks>
/// </returns>
public override string FormatAddress(string area, int address, int subAddress) public override string FormatAddress(string area, int address, int subAddress)
{ {
return area + " " + address + "." + subAddress; return area + " " + address + "." + subAddress;

View File

@@ -1,45 +1,122 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net.Modbus.NA200H namespace Modbus.Net.Modbus.NA200H
{ {
/// <summary> /// <summary>
/// 南大奥拓NA200H数据单元翻译器 /// 南大奥拓 NA200H 数据单元翻译器 / Nanda Aotuo NA200H Data Unit Translator
/// <remarks>
/// 实现 NA200H 专用协议的地址翻译功能,将 NA200H 地址映射到标准 Modbus 地址
/// Implements address translation functionality for NA200H dedicated protocol, maps NA200H addresses to standard Modbus addresses
/// <para>
/// NA200H 协议特点 / NA200H Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 Modbus 协议的扩展 / Extension based on Modbus protocol</item>
/// <item>多种数据区域映射到 Modbus 地址 / Multiple data areas map to Modbus addresses</item>
/// <item>支持线圈和寄存器混合寻址 / Supports coil and register mixed addressing</item>
/// </list>
/// </para>
/// <para>
/// 地址映射表 / Address Mapping Table:
/// <list type="bullet">
/// <item><strong>Q (输出继电器)</strong> → Modbus 线圈 0-9999 / Modbus coils 0-9999</item>
/// <item><strong>M (辅助继电器)</strong> → Modbus 线圈 10000-19999 / Modbus coils 10000-19999</item>
/// <item><strong>N (特殊继电器)</strong> → Modbus 线圈 30000-39999 / Modbus coils 30000-39999</item>
/// <item><strong>I (输入继电器)</strong> → Modbus 离散输入 0-9999 / Modbus discrete inputs 0-9999</item>
/// <item><strong>S (状态继电器)</strong> → Modbus 离散输入 10000-19999 / Modbus discrete inputs 10000-19999</item>
/// <item><strong>IW (输入寄存器)</strong> → Modbus 输入寄存器 0-4999 / Modbus input registers 0-4999</item>
/// <item><strong>SW (特殊寄存器)</strong> → Modbus 输入寄存器 5000-9999 / Modbus input registers 5000-9999</item>
/// <item><strong>MW (辅助寄存器)</strong> → Modbus 保持寄存器 0-9999 / Modbus holding registers 0-9999</item>
/// <item><strong>QW (输出寄存器)</strong> → Modbus 保持寄存器 20000-29999 / Modbus holding registers 20000-29999</item>
/// <item><strong>NW (特殊寄存器)</strong> → Modbus 保持寄存器 21000-21999 / Modbus holding registers 21000-21999</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var translator = new AddressTranslatorNA200H();
///
/// // 翻译 NA200H 地址 / Translate NA200H address
/// AddressDef addr1 = translator.AddressTranslate("Q 1", isRead: true);
/// // 结果AreaString="Q", Area=1 (功能码 01), Address=0
///
/// AddressDef addr2 = translator.AddressTranslate("MW 100", isRead: true);
/// // 结果AreaString="MW", Area=3 (功能码 03), Address=99
///
/// AddressDef addr3 = translator.AddressTranslate("IW 50", isRead: true);
/// // 结果AreaString="IW", Area=4 (功能码 04), Address=49
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressTranslatorNA200H : ModbusTranslatorBase public class AddressTranslatorNA200H : ModbusTranslatorBase
{ {
/// <summary> /// <summary>
/// 读功能码 /// 读功能码字典 / Read Function Code Dictionary
/// <remarks>
/// 存储各区域的读功能码和字节宽度
/// Stores read function codes and byte widths for each area
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary; protected Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary;
/// <summary> /// <summary>
/// 功能码翻译至标准Modbus地址位置 /// 地址转换字典 / Address Translation Dictionary
/// <remarks>
/// 将 NA200H 区域映射到标准 Modbus 地址偏移
/// Maps NA200H areas to standard Modbus address offsets
/// <para>
/// 映射规则 / Mapping Rules:
/// <list type="bullet">
/// <item>Q: 0 (线圈 0-9999)</item>
/// <item>M: 10000 (线圈 10000-19999)</item>
/// <item>N: 30000 (线圈 30000-39999)</item>
/// <item>I: 0 (离散输入 0-9999)</item>
/// <item>S: 10000 (离散输入 10000-19999)</item>
/// <item>IW: 0 (输入寄存器 0-4999)</item>
/// <item>SW: 5000 (输入寄存器 5000-9999)</item>
/// <item>MW: 0 (保持寄存器 0-9999)</item>
/// <item>QW: 20000 (保持寄存器 20000-29999)</item>
/// <item>NW: 21000 (保持寄存器 21000-21999)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<string, int> TransDictionary; protected Dictionary<string, int> TransDictionary;
/// <summary> /// <summary>
/// 写功能码 /// 写功能码字典 / Write Function Code Dictionary
/// <remarks>
/// 存储各区域的写功能码和字节宽度,区分单写和多写
/// Stores write function codes and byte widths for each area, distinguishing single and multi-write
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary; protected Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary;
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 NA200H 地址翻译器,配置所有映射关系
/// Initialize NA200H address translator, configure all mapping relationships
/// </remarks>
/// </summary> /// </summary>
public AddressTranslatorNA200H() public AddressTranslatorNA200H()
{ {
// 初始化地址转换字典 / Initialize address translation dictionary
TransDictionary = new Dictionary<string, int> TransDictionary = new Dictionary<string, int>
{ {
{"Q", 0}, {"Q", 0}, // 输出继电器 0-9999 / Output relay 0-9999
{"M", 10000}, {"M", 10000}, // 辅助继电器 10000-19999 / Auxiliary relay 10000-19999
{"N", 30000}, {"N", 30000}, // 特殊继电器 30000-39999 / Special relay 30000-39999
{"I", 0}, {"I", 0}, // 输入继电器 0-9999 / Input relay 0-9999
{"S", 10000}, {"S", 10000}, // 状态继电器 10000-19999 / Status relay 10000-19999
{"IW", 0}, {"IW", 0}, // 输入寄存器 0-4999 / Input register 0-4999
{"SW", 5000}, {"SW", 5000}, // 特殊寄存器 5000-9999 / Special register 5000-9999
{"MW", 0}, {"MW", 0}, // 辅助寄存器 0-9999 / Auxiliary register 0-9999
{"QW", 20000}, {"QW", 20000}, // 输出寄存器 20000-29999 / Output register 20000-29999
{"NW", 21000} {"NW", 21000} // 特殊寄存器 21000-21999 / Special register 21000-21999
}; };
// 初始化读功能码字典 / Initialize read function code dictionary
ReadFunctionCodeDictionary = new Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary = new Dictionary<string, AreaOutputDef>
{ {
{ {
@@ -103,6 +180,8 @@ namespace Modbus.Net.Modbus.NA200H
new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadHoldRegister, AreaWidth = 2} new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadHoldRegister, AreaWidth = 2}
} }
}; };
// 初始化写功能码字典 / Initialize write function code dictionary
WriteFunctionCodeDictionary = new Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary = new Dictionary<(string, bool), AreaOutputDef>
{ {
{ {
@@ -205,12 +284,58 @@ namespace Modbus.Net.Modbus.NA200H
} }
/// <summary> /// <summary>
/// 地址转换 /// 地址转换 / Address Translate
/// <remarks>
/// 将 NA200H 格式的地址字符串翻译为 AddressDef 对象
/// Translate NA200H format address string to AddressDef object
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>转为大写 / Convert to uppercase</item>
/// <item>按空格分割 / Split by space</item>
/// <item>提取区域 (head) 和地址 (tail) / Extract area (head) and address (tail)</item>
/// <item>处理子地址 (位偏移) / Handle sub-address (bit offset)</item>
/// <item>查表获取 Modbus 地址偏移 / Lookup Modbus address offset</item>
/// <item>计算最终 Modbus 地址 / Calculate final Modbus address</item>
/// <item>创建并返回 AddressDef / Create and return AddressDef</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="address">格式化的地址</param> /// <param name="address">
/// <param name="isRead">是否为读取,是为读取,否为写入</param> /// 格式化的地址 / Formatted Address
/// <param name="isSingle">是否只写入一个数据</param> /// <remarks>
/// <returns>翻译后的地址</returns> /// NA200H 格式,如 "Q 1", "MW 100", "IW 50.3" 等
/// NA200H format, e.g., "Q 1", "MW 100", "IW 50.3", etc.
/// </remarks>
/// </param>
/// <param name="isRead">
/// 是否为读取 / Whether it's Read
/// <remarks>
/// true: 读取操作 / Read operation
/// false: 写入操作 / Write operation
/// </remarks>
/// </param>
/// <param name="isSingle">
/// 是否只写入一个数据 / Whether Writing Single Data
/// <remarks>
/// true: 单个写入 / Single write
/// false: 多个写入 / Multi write
/// </remarks>
/// </param>
/// <returns>
/// 翻译后的地址 / Translated Address
/// <remarks>
/// AddressDef 包含:
/// AddressDef contains:
/// <list type="bullet">
/// <item>AreaString: NA200H 区域字符串 / NA200H area string</item>
/// <item>Area: Modbus 功能码 / Modbus function code</item>
/// <item>Address: Modbus 地址 (从 0 开始) / Modbus address (starts from 0)</item>
/// <item>SubAddress: 子地址 (位偏移) / Sub-address (bit offset)</item>
/// </list>
/// </remarks>
/// </returns>
public override AddressDef AddressTranslate(string address, bool isRead, bool isSingle) public override AddressDef AddressTranslate(string address, bool isRead, bool isSingle)
{ {
address = address.ToUpper(); address = address.ToUpper();
@@ -246,10 +371,35 @@ namespace Modbus.Net.Modbus.NA200H
} }
/// <summary> /// <summary>
/// 获取区域中的单个地址占用的字节长度 /// 获取区域字节长度 / Get Area Byte Length
/// <remarks>
/// 返回 NA200H 区域中单个地址占用的字节长度
/// Returns byte length per address in NA200H area
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item>线圈/继电器 (Q/M/N/I/S): 0.125 字节 (1 位) / Coils/Relays: 0.125 bytes (1 bit)</item>
/// <item>寄存器 (IW/SW/MW/QW/NW): 2 字节 (16 位) / Registers: 2 bytes (16 bits)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">区域名称</param> /// <param name="area">
/// <returns>字节长度</returns> /// 区域名称 / Area Name
/// <remarks>
/// NA200H 区域标识
/// NA200H area identifier
/// </remarks>
/// </param>
/// <returns>
/// 字节长度 / Byte Length
/// <remarks>
/// <list type="bullet">
/// <item>线圈/继电器0.125 字节</item>
/// <item>寄存器2 字节</item>
/// </list>
/// </remarks>
/// </returns>
public override double GetAreaByteLength(string area) public override double GetAreaByteLength(string area)
{ {
return ReadFunctionCodeDictionary[area].AreaWidth; return ReadFunctionCodeDictionary[area].AreaWidth;

View File

@@ -1,24 +1,155 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Modbus.SelfDefinedSample namespace Modbus.Net.Modbus.SelfDefinedSample
{ {
/// <summary> /// <summary>
/// Utility时间读写接口 /// Utility 时间读写接口 / Utility Time Read/Write Interface
/// <remarks>
/// 定义 Modbus 设备时间读写的高级接口
/// Defines high-level interface for Modbus device time read/write
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item><strong>GetTimeAsync</strong> - 读取 PLC 系统时间 / Read PLC system time</item>
/// <item><strong>SetTimeAsync</strong> - 设置 PLC 系统时间 / Set PLC system time</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>设备时间同步 / Device time synchronization</item>
/// <item>时间戳校准 / Timestamp calibration</item>
/// <item>日志时间统一 / Log time unification</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建支持时间的 Utility / Create time-enabled Utility
/// var utility = new ModbusUtilityTime(
/// ModbusType.Tcp,
/// "192.168.1.100:502",
/// slaveAddress: 1,
/// masterAddress: 0,
/// endian: Endian.BigEndianLsb
/// );
///
/// // 读取 PLC 时间 / Read PLC time
/// var timeResult = await utility.GetTimeAsync(startAddress: 0);
/// if (timeResult.IsSuccess)
/// {
/// Console.WriteLine($"PLC 时间:{timeResult.Datas}");
/// }
///
/// // 设置 PLC 时间 / Set PLC time
/// await utility.SetTimeAsync(startAddress: 0, DateTime.Now);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodTime : IUtilityMethod public interface IUtilityMethodTime : IUtilityMethod
{ {
/// <summary> /// <summary>
/// 获取PLC时间 /// 获取 PLC 时间 / Get PLC Time
/// <remarks>
/// 从 Modbus 设备读取当前系统时间
/// Read current system time from Modbus device
/// <para>
/// 时间格式 / Time Format:
/// <list type="bullet">
/// <item>年 (2 字节) / Year (2 bytes)</item>
/// <item>月 (1 字节) / Month (1 byte)</item>
/// <item>日 (1 字节) / Day (1 byte)</item>
/// <item>时 (2 字节) / Hour (2 bytes)</item>
/// <item>分 (1 字节) / Minute (1 byte)</item>
/// <item>秒 (1 字节) / Second (1 byte)</item>
/// <item>毫秒 (2 字节) / Millisecond (2 bytes)</item>
/// </list>
/// </para>
/// <para>
/// 共 10 字节,占用 5 个寄存器 / Total 10 bytes, occupies 5 registers
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns>PLC时间</returns> /// <param name="startAddress">
/// 起始地址 / Start Address
/// <remarks>
/// 时间寄存器起始地址
/// Starting address of time registers
/// <para>
/// 通常为 0 或设备定义的地址
/// Usually 0 or device-defined address
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// PLC 时间 / PLC Time
/// <remarks>
/// ReturnStruct&lt;DateTime&gt; 包含:
/// ReturnStruct&lt;DateTime&gt; contains:
/// <list type="bullet">
/// <item>Datas: 读取的时间值 / Read time value</item>
/// <item>IsSuccess: 读取是否成功 / Read success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
Task<ReturnStruct<DateTime>> GetTimeAsync(ushort startAddress); Task<ReturnStruct<DateTime>> GetTimeAsync(ushort startAddress);
/// <summary> /// <summary>
/// 设置PLC时间 /// 设置 PLC 时间 / Set PLC Time
/// <remarks>
/// 向 Modbus 设备写入系统时间
/// Write system time to Modbus device
/// <para>
/// 时间格式 / Time Format:
/// <list type="bullet">
/// <item>年 (2 字节) / Year (2 bytes)</item>
/// <item>月 (1 字节) / Month (1 byte)</item>
/// <item>日 (1 字节) / Day (1 byte)</item>
/// <item>时 (2 字节) / Hour (2 bytes)</item>
/// <item>分 (1 字节) / Minute (1 byte)</item>
/// <item>秒 (1 字节) / Second (1 byte)</item>
/// <item>毫秒 (2 字节) / Millisecond (2 bytes)</item>
/// </list>
/// </para>
/// <para>
/// 共 10 字节,占用 5 个寄存器 / Total 10 bytes, occupies 5 registers
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="setTime">设置PLC时间</param> /// <param name="startAddress">
/// <returns>设置是否成功</returns> /// 起始地址 / Start Address
/// <remarks>
/// 时间寄存器起始地址
/// Starting address of time registers
/// </remarks>
/// </param>
/// <param name="setTime">
/// 设置 PLC 时间 / PLC Time to Set
/// <remarks>
/// 要写入的 DateTime 对象
/// DateTime object to write
/// <para>
/// 包含年月日时分秒毫秒
/// Contains year, month, day, hour, minute, second, millisecond
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 设置是否成功 / Whether Set is Successful
/// <remarks>
/// ReturnStruct&lt;bool&gt;:
/// <list type="bullet">
/// <item>Datas: true=成功false=失败 / true=success, false=failure</item>
/// <item>IsSuccess: 操作是否成功 / Operation success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
Task<ReturnStruct<bool>> SetTimeAsync(ushort startAddress, DateTime setTime); Task<ReturnStruct<bool>> SetTimeAsync(ushort startAddress, DateTime setTime);
} }
} }

View File

@@ -1,82 +1,202 @@
using System; using System;
using ProtocolUnit = Modbus.Net.ProtocolUnit<byte[], byte[]>; using ProtocolUnit = Modbus.Net.ProtocolUnit<byte[], byte[]>;
namespace Modbus.Net.Modbus.SelfDefinedSample namespace Modbus.Net.Modbus.SelfDefinedSample
{ {
/// <summary> /// <summary>
/// 跟时间有关的功能码 /// Modbus 时间协议功能码枚举 / Modbus Time Protocol Function Code Enum
/// <remarks>
/// 定义与时间读写相关的自定义功能码
/// Defines custom function codes related to time read/write
/// <para>
/// 注意 / Note:
/// <list type="bullet">
/// <item>这些是自定义功能码,非标准 Modbus 功能码</item>
/// <item>These are custom function codes, not standard Modbus function codes</item>
/// <item>实际使用时需确认设备支持的功能码</item>
/// <item>Confirm device-supported function codes before actual use</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public enum ModbusProtocolTimeFunctionCode : byte public enum ModbusProtocolTimeFunctionCode : byte
{ {
/// <summary> /// <summary>
/// 读时间 /// 读系统时间 (功能码 3) / Get System Time (Function Code 3)
/// <remarks>
/// 读取设备的当前系统时间
/// Read device's current system time
/// <para>
/// 请求数据 / Request Data:
/// <list type="bullet">
/// <item>从站地址 (1 字节) / Slave address (1 byte)</item>
/// <item>功能码 3 (1 字节) / Function code 3 (1 byte)</item>
/// <item>起始地址 (2 字节) / Start address (2 bytes)</item>
/// <item>读取数量 5 (2 字节) / Read count 5 (2 bytes)</item>
/// </list>
/// </para>
/// <para>
/// 响应数据 / Response Data:
/// <list type="bullet">
/// <item>从站地址 (1 字节) / Slave address (1 byte)</item>
/// <item>功能码 3 (1 字节) / Function code 3 (1 byte)</item>
/// <item>字节数 10 (1 字节) / Byte count 10 (1 byte)</item>
/// <item>年 (2 字节) / Year (2 bytes)</item>
/// <item>日 (1 字节) / Day (1 byte)</item>
/// <item>月 (1 字节) / Month (1 byte)</item>
/// <item>时 (2 字节) / Hour (2 bytes)</item>
/// <item>秒 (1 字节) / Second (1 byte)</item>
/// <item>分 (1 字节) / Minute (1 byte)</item>
/// <item>毫秒 (2 字节) / Millisecond (2 bytes)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
GetSystemTime = 3, GetSystemTime = 3,
/// <summary> /// <summary>
/// 写时间 /// 写系统时间 (功能码 16) / Set System Time (Function Code 16)
/// <remarks>
/// 写入设备的系统时间
/// Write device's system time
/// <para>
/// 请求数据 / Request Data:
/// <list type="bullet">
/// <item>从站地址 (1 字节) / Slave address (1 byte)</item>
/// <item>功能码 16 (1 字节) / Function code 16 (1 byte)</item>
/// <item>起始地址 (2 字节) / Start address (2 bytes)</item>
/// <item>写入数量 5 (2 字节) / Write count 5 (2 bytes)</item>
/// <item>字节数 10 (1 字节) / Byte count 10 (1 byte)</item>
/// <item>年 (2 字节) / Year (2 bytes)</item>
/// <item>日 (1 字节) / Day (1 byte)</item>
/// <item>月 (1 字节) / Month (1 byte)</item>
/// <item>时 (2 字节) / Hour (2 bytes)</item>
/// <item>秒 (1 字节) / Second (1 byte)</item>
/// <item>分 (1 字节) / Minute (1 byte)</item>
/// <item>毫秒 (2 字节) / Millisecond (2 bytes)</item>
/// </list>
/// </para>
/// <para>
/// 响应数据 / Response Data:
/// <list type="bullet">
/// <item>从站地址 (1 字节) / Slave address (1 byte)</item>
/// <item>功能码 16 (1 字节) / Function code 16 (1 byte)</item>
/// <item>起始地址 (2 字节) / Start address (2 bytes)</item>
/// <item>写入数量 5 (2 字节) / Write count 5 (2 bytes)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
SetSystemTime = 16 SetSystemTime = 16
} }
#region PLC时间 #region PLC / Read PLC Time
/// <summary> /// <summary>
/// 读时间输入 /// 读时间输入结构 / Read Time Input Structure
/// <remarks>
/// 封装读取设备时间的请求参数
/// Encapsulates request parameters for reading device time
/// </remarks>
/// </summary> /// </summary>
public class GetSystemTimeModbusInputStruct : IInputStruct public class GetSystemTimeModbusInputStruct : IInputStruct
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化读时间请求参数
/// Initialize read time request parameters
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// Modbus 从站地址,范围 1-247
/// Modbus slave address, range 1-247
/// </remarks>
/// </param>
/// <param name="startAddress">
/// 起始地址 / Start Address
/// <remarks>
/// 时间寄存器起始地址
/// Starting address of time registers
/// </remarks>
/// </param>
public GetSystemTimeModbusInputStruct(byte slaveAddress, ushort startAddress) public GetSystemTimeModbusInputStruct(byte slaveAddress, ushort startAddress)
{ {
SlaveAddress = slaveAddress; SlaveAddress = slaveAddress;
FunctionCode = (byte)ModbusProtocolTimeFunctionCode.GetSystemTime; FunctionCode = (byte)ModbusProtocolTimeFunctionCode.GetSystemTime;
StartAddress = startAddress; StartAddress = startAddress;
GetCount = 5; GetCount = 5; // 时间占用 5 个寄存器 / Time occupies 5 registers
} }
/// <summary> /// <summary>
/// 从站号 /// 从站号 / Slave Address
/// <remarks>
/// Modbus 从站地址
/// Modbus slave address
/// </remarks>
/// </summary> /// </summary>
public byte SlaveAddress { get; } public byte SlaveAddress { get; }
/// <summary> /// <summary>
/// 功能码 /// 功能码 / Function Code
/// <remarks>
/// 读时间功能码 (3)
/// Read time function code (3)
/// </remarks>
/// </summary> /// </summary>
public byte FunctionCode { get; } public byte FunctionCode { get; }
/// <summary> /// <summary>
/// 开始地址 /// 开始地址 / Start Address
/// <remarks>
/// 时间寄存器起始地址
/// Starting address of time registers
/// </remarks>
/// </summary> /// </summary>
public ushort StartAddress { get; } public ushort StartAddress { get; }
/// <summary> /// <summary>
/// 获取个数 /// 获取个数 / Get Count
/// <remarks>
/// 读取寄存器数量 (5 个)
/// Number of registers to read (5)
/// <para>
/// 年 (1) + 日月 (2) + 时分 (2) + 秒毫秒 (2) = 5 个寄存器
/// Year (1) + Day/Month (2) + Hour/Minute (2) + Second/Millisecond (2) = 5 registers
/// </para>
/// </remarks>
/// </summary> /// </summary>
public ushort GetCount { get; } public ushort GetCount { get; }
} }
/// <summary> /// <summary>
/// 读时间输出 /// 读时间输出结构 / Read Time Output Structure
/// <remarks>
/// 封装读取设备时间的响应数据
/// Encapsulates response data for reading device time
/// </remarks>
/// </summary> /// </summary>
public class GetSystemTimeModbusOutputStruct : IOutputStruct public class GetSystemTimeModbusOutputStruct : IOutputStruct
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化读时间响应数据
/// Initialize read time response data
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="functionCode">功能码</param> /// <param name="functionCode">功能码 / Function Code</param>
/// <param name="writeByteCount">写入个数</param> /// <param name="writeByteCount">写入个数 / Write Byte Count</param>
/// <param name="year">年</param> /// <param name="year">年 / Year</param>
/// <param name="day">日</param> /// <param name="day">日 / Day</param>
/// <param name="month">月</param> /// <param name="month">月 / Month</param>
/// <param name="hour">时</param> /// <param name="hour">时 / Hour</param>
/// <param name="second">秒</param> /// <param name="second">秒 / Second</param>
/// <param name="minute">分</param> /// <param name="minute">分 / Minute</param>
/// <param name="millisecond">毫秒</param> /// <param name="millisecond">毫秒 / Millisecond</param>
public GetSystemTimeModbusOutputStruct(byte slaveAddress, byte functionCode, public GetSystemTimeModbusOutputStruct(byte slaveAddress, byte functionCode,
byte writeByteCount, ushort year, byte day, byte month, ushort hour, byte second, byte minute, byte writeByteCount, ushort year, byte day, byte month, ushort hour, byte second, byte minute,
ushort millisecond) ushort millisecond)
@@ -88,36 +208,64 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 从站号 /// 从站号 / Slave Address
/// </summary> /// </summary>
public byte SlaveAddress { get; private set; } public byte SlaveAddress { get; private set; }
/// <summary> /// <summary>
/// 功能码 /// 功能码 / Function Code
/// </summary> /// </summary>
public byte FunctionCode { get; private set; } public byte FunctionCode { get; private set; }
/// <summary> /// <summary>
/// 写入个数 /// 写入个数 / Write Byte Count
/// </summary> /// </summary>
public byte WriteByteCount { get; private set; } public byte WriteByteCount { get; private set; }
/// <summary> /// <summary>
/// 时间 /// 时间 / Time
/// <remarks>
/// 解析后的 DateTime 对象
/// Parsed DateTime object
/// </remarks>
/// </summary> /// </summary>
public DateTime Time { get; private set; } public DateTime Time { get; private set; }
} }
/// <summary> /// <summary>
/// 读系统时间协议 /// 读系统时间协议类 / Read System Time Protocol Class
/// <remarks>
/// 实现读系统时间的协议格式化和反格式化
/// Implements protocol formatting and unformatting for reading system time
/// </remarks>
/// </summary> /// </summary>
public class GetSystemTimeModbusProtocol : ProtocolUnit public class GetSystemTimeModbusProtocol : ProtocolUnit
{ {
/// <summary> /// <summary>
/// 格式化 /// 格式化 (发送前) / Format (Before Sending)
/// <remarks>
/// 将读时间请求参数转换为字节数组
/// Convert read time request parameters to byte array
/// <para>
/// 格式 / Format:
/// <code>[从站地址][功能码][起始地址 (2)][读取数量 (2)]</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="message">写系统时间参数</param> /// <param name="message">
/// <returns>写系统时间的核心</returns> /// 读时间输入结构 / Read Time Input Structure
/// <remarks>
/// 包含从站地址、起始地址等参数
/// Contains slave address, start address, etc.
/// </remarks>
/// </param>
/// <returns>
/// 格式化后的字节数组 / Formatted Byte Array
/// <remarks>
/// Modbus 读时间请求帧
/// Modbus read time request frame
/// </remarks>
/// </returns>
public override byte[] Format(IInputStruct message) public override byte[] Format(IInputStruct message)
{ {
var r_message = (GetSystemTimeModbusInputStruct)message; var r_message = (GetSystemTimeModbusInputStruct)message;
@@ -126,11 +274,48 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 反格式化 /// 反格式化 (接收后) / Unformat (After Receiving)
/// <remarks>
/// 将响应字节数组解析为读时间输出结构
/// Parse response byte array to read time output structure
/// <para>
/// 解析顺序 / Parse Order:
/// <list type="number">
/// <item>从站地址 (1 字节) / Slave address (1 byte)</item>
/// <item>功能码 (1 字节) / Function code (1 byte)</item>
/// <item>字节数 (1 字节) / Byte count (1 byte)</item>
/// <item>年 (2 字节) / Year (2 bytes)</item>
/// <item>日 (1 字节) / Day (1 byte)</item>
/// <item>月 (1 字节) / Month (1 byte)</item>
/// <item>时 (2 字节) / Hour (2 bytes)</item>
/// <item>秒 (1 字节) / Second (1 byte)</item>
/// <item>分 (1 字节) / Minute (1 byte)</item>
/// <item>毫秒 (2 字节) / Millisecond (2 bytes)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="messageBytes">获取的信息</param> /// <param name="messageBytes">
/// <param name="flag">当前反格式化的位置</param> /// 获取的信息 / Received Information
/// <returns>反格式化的信息</returns> /// <remarks>
/// 设备返回的字节数组
/// Byte array returned from device
/// </remarks>
/// </param>
/// <param name="flag">
/// 当前反格式化的位置 / Current Unformat Position
/// <remarks>
/// 引用传递,自动更新
/// Passed by reference, auto-updated
/// </remarks>
/// </param>
/// <returns>
/// 反格式化的信息 / Unformatted Information
/// <remarks>
/// GetSystemTimeModbusOutputStruct 对象
/// GetSystemTimeModbusOutputStruct object
/// </remarks>
/// </returns>
public override IOutputStruct Unformat(byte[] messageBytes, ref int flag) public override IOutputStruct Unformat(byte[] messageBytes, ref int flag)
{ {
var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag); var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
@@ -150,25 +335,34 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
#endregion #endregion
#region PLC时间 #region PLC / Write PLC Time
/// <summary> /// <summary>
/// 写时间输入 /// 写时间输入结构 / Write Time Input Structure
/// <remarks>
/// 封装写入设备时间的请求参数
/// Encapsulates request parameters for writing device time
/// </remarks>
/// </summary> /// </summary>
public class SetSystemTimeModbusInputStruct : IInputStruct public class SetSystemTimeModbusInputStruct : IInputStruct
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化写时间请求参数,从 DateTime 提取各时间分量
/// Initialize write time request parameters, extract time components from DateTime
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="time">时间</param> /// <param name="startAddress">起始地址 / Start Address</param>
/// <param name="time">时间 / Time</param>
public SetSystemTimeModbusInputStruct(byte slaveAddress, ushort startAddress, DateTime time) public SetSystemTimeModbusInputStruct(byte slaveAddress, ushort startAddress, DateTime time)
{ {
SlaveAddress = slaveAddress; SlaveAddress = slaveAddress;
FunctionCode = (byte)ModbusProtocolTimeFunctionCode.SetSystemTime; FunctionCode = (byte)ModbusProtocolTimeFunctionCode.SetSystemTime;
StartAddress = startAddress; StartAddress = startAddress;
WriteCount = 5; WriteCount = 5; // 时间占用 5 个寄存器 / Time occupies 5 registers
WriteByteCount = 10; WriteByteCount = 10; // 10 字节数据 / 10 bytes data
Year = (ushort)time.Year; Year = (ushort)time.Year;
Day = (byte)time.Day; Day = (byte)time.Day;
Month = (byte)time.Month; Month = (byte)time.Month;
@@ -179,78 +373,86 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 从站号 /// 从站号 / Slave Address
/// </summary> /// </summary>
public byte SlaveAddress { get; } public byte SlaveAddress { get; }
/// <summary> /// <summary>
/// 功能码 /// 功能码 / Function Code
/// </summary> /// </summary>
public byte FunctionCode { get; } public byte FunctionCode { get; }
/// <summary> /// <summary>
/// 开始地址 /// 开始地址 / Start Address
/// </summary> /// </summary>
public ushort StartAddress { get; } public ushort StartAddress { get; }
/// <summary> /// <summary>
/// 写入个数 /// 写入个数 / Write Count
/// </summary> /// </summary>
public ushort WriteCount { get; } public ushort WriteCount { get; }
/// <summary> /// <summary>
/// 写入字节个数 /// 写入字节个数 / Write Byte Count
/// </summary> /// </summary>
public byte WriteByteCount { get; } public byte WriteByteCount { get; }
/// <summary> /// <summary>
/// 年 /// 年 / Year
/// </summary> /// </summary>
public ushort Year { get; } public ushort Year { get; }
/// <summary> /// <summary>
/// 日 /// 日 / Day
/// </summary> /// </summary>
public byte Day { get; } public byte Day { get; }
/// <summary> /// <summary>
/// 月 /// 月 / Month
/// </summary> /// </summary>
public byte Month { get; } public byte Month { get; }
/// <summary> /// <summary>
/// 时 /// 时 / Hour
/// </summary> /// </summary>
public ushort Hour { get; } public ushort Hour { get; }
/// <summary> /// <summary>
/// 秒 /// 秒 / Second
/// </summary> /// </summary>
public byte Second { get; } public byte Second { get; }
/// <summary> /// <summary>
/// 分 /// 分 / Minute
/// </summary> /// </summary>
public byte Minute { get; } public byte Minute { get; }
/// <summary> /// <summary>
/// 毫秒 /// 毫秒 / Millisecond
/// </summary> /// </summary>
public ushort Millisecond { get; } public ushort Millisecond { get; }
} }
/// <summary> /// <summary>
/// 写时间输出 /// 写时间输出结构 / Write Time Output Structure
/// <remarks>
/// 封装写入设备时间的响应数据
/// Encapsulates response data for writing device time
/// </remarks>
/// </summary> /// </summary>
public class SetSystemTimeModbusOutputStruct : IOutputStruct public class SetSystemTimeModbusOutputStruct : IOutputStruct
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化写时间响应数据
/// Initialize write time response data
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="functionCode">功能码</param> /// <param name="functionCode">功能码 / Function Code</param>
/// <param name="startAddress">开始地址</param> /// <param name="startAddress">开始地址 / Start Address</param>
/// <param name="writeCount">写入个数</param> /// <param name="writeCount">写入个数 / Write Count</param>
public SetSystemTimeModbusOutputStruct(byte slaveAddress, byte functionCode, public SetSystemTimeModbusOutputStruct(byte slaveAddress, byte functionCode,
ushort startAddress, ushort writeCount) ushort startAddress, ushort writeCount)
{ {
@@ -261,36 +463,60 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 从站号 /// 从站号 / Slave Address
/// </summary> /// </summary>
public byte SlaveAddress { get; private set; } public byte SlaveAddress { get; private set; }
/// <summary> /// <summary>
/// 功能码 /// 功能码 / Function Code
/// </summary> /// </summary>
public byte FunctionCode { get; private set; } public byte FunctionCode { get; private set; }
/// <summary> /// <summary>
/// 开始地址 /// 开始地址 / Start Address
/// </summary> /// </summary>
public ushort StartAddress { get; private set; } public ushort StartAddress { get; private set; }
/// <summary> /// <summary>
/// 写入个数 /// 写入个数 / Write Count
/// </summary> /// </summary>
public ushort WriteCount { get; private set; } public ushort WriteCount { get; private set; }
} }
/// <summary> /// <summary>
/// 写系统时间协议 /// 写系统时间协议类 / Write System Time Protocol Class
/// <remarks>
/// 实现写系统时间的协议格式化和反格式化
/// Implements protocol formatting and unformatting for writing system time
/// </remarks>
/// </summary> /// </summary>
public class SetSystemTimeModbusProtocol : ProtocolUnit public class SetSystemTimeModbusProtocol : ProtocolUnit
{ {
/// <summary> /// <summary>
/// 格式化 /// 格式化 (发送前) / Format (Before Sending)
/// <remarks>
/// 将写时间请求参数转换为字节数组
/// Convert write time request parameters to byte array
/// <para>
/// 格式 / Format:
/// <code>[从站地址][功能码][起始地址 (2)][写入数量 (2)][字节数 (1)][年 (2)][日 (1)][月 (1)][时 (2)][秒 (1)][分 (1)][毫秒 (2)]</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="message">写系统时间的参数</param> /// <param name="message">
/// <returns>写系统时间的核心</returns> /// 写时间输入结构 / Write Time Input Structure
/// <remarks>
/// 包含从站地址、时间等参数
/// Contains slave address, time, etc.
/// </remarks>
/// </param>
/// <returns>
/// 格式化后的字节数组 / Formatted Byte Array
/// <remarks>
/// Modbus 写时间请求帧
/// Modbus write time request frame
/// </remarks>
/// </returns>
public override byte[] Format(IInputStruct message) public override byte[] Format(IInputStruct message)
{ {
var r_message = (SetSystemTimeModbusInputStruct)message; var r_message = (SetSystemTimeModbusInputStruct)message;
@@ -301,11 +527,39 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 反格式化 /// 反格式化 (接收后) / Unformat (After Receiving)
/// <remarks>
/// 将响应字节数组解析为写时间输出结构
/// Parse response byte array to write time output structure
/// <para>
/// 解析顺序 / Parse Order:
/// <list type="number">
/// <item>从站地址 (1 字节) / Slave address (1 byte)</item>
/// <item>功能码 (1 字节) / Function code (1 byte)</item>
/// <item>起始地址 (2 字节) / Start address (2 bytes)</item>
/// <item>写入数量 (2 字节) / Write count (2 bytes)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="messageBytes">获取的信息</param> /// <param name="messageBytes">
/// <param name="flag">当前反格式化的位置</param> /// 获取的信息 / Received Information
/// <returns>反格式化的信息</returns> /// <remarks>
/// 设备返回的字节数组
/// Byte array returned from device
/// </remarks>
/// </param>
/// <param name="flag">
/// 当前反格式化的位置 / Current Unformat Position
/// <remarks>引用传递,自动更新 / Passed by reference, auto-updated</remarks>
/// </param>
/// <returns>
/// 反格式化的信息 / Unformatted Information
/// <remarks>
/// SetSystemTimeModbusOutputStruct 对象
/// SetSystemTimeModbusOutputStruct object
/// </remarks>
/// </returns>
public override IOutputStruct Unformat(byte[] messageBytes, ref int flag) public override IOutputStruct Unformat(byte[] messageBytes, ref int flag)
{ {
var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag); var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);

View File

@@ -1,20 +1,95 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Modbus.SelfDefinedSample namespace Modbus.Net.Modbus.SelfDefinedSample
{ {
/// <summary>
/// Modbus 时间工具类 / Modbus Time Utility Class
/// <remarks>
/// 提供 Modbus 设备时间读写功能,继承自 ModbusUtility
/// Provides Modbus device time read/write functionality, inherits from ModbusUtility
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item><strong>GetTimeAsync</strong> - 读取 PLC 系统时间 / Read PLC system time</item>
/// <item><strong>SetTimeAsync</strong> - 设置 PLC 系统时间 / Set PLC system time</item>
/// </list>
/// </para>
/// <para>
/// 时间格式 / Time Format:
/// <list type="bullet">
/// <item>年 (2 字节) / Year (2 bytes)</item>
/// <item>月 (1 字节) / Month (1 byte)</item>
/// <item>日 (1 字节) / Day (1 byte)</item>
/// <item>时 (2 字节) / Hour (2 bytes)</item>
/// <item>分 (1 字节) / Minute (1 byte)</item>
/// <item>秒 (1 字节) / Second (1 byte)</item>
/// <item>毫秒 (2 字节) / Millisecond (2 bytes)</item>
/// </list>
/// 共 10 字节,占用 5 个寄存器 / Total 10 bytes, occupies 5 registers
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建时间工具实例 / Create time utility instance
/// var utility = new ModbusUtilityTime(
/// ModbusType.Tcp,
/// "192.168.1.100:502",
/// slaveAddress: 1,
/// masterAddress: 0,
/// endian: Endian.BigEndianLsb
/// );
///
/// // 连接设备 / Connect to device
/// await utility.ConnectAsync();
///
/// // 读取 PLC 时间 / Read PLC time
/// var timeResult = await utility.GetTimeAsync(startAddress: 0);
/// if (timeResult.IsSuccess)
/// {
/// Console.WriteLine($"PLC 时间:{timeResult.Datas}");
/// }
///
/// // 同步 PLC 时间 / Sync PLC time
/// await utility.SetTimeAsync(startAddress: 0, DateTime.Now);
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class ModbusUtilityTime : ModbusUtility, IUtilityMethodTime public class ModbusUtilityTime : ModbusUtility, IUtilityMethodTime
{ {
private static readonly ILogger<ModbusUtility> logger = LogProvider.CreateLogger<ModbusUtility>(); private static readonly ILogger<ModbusUtility> logger = LogProvider.CreateLogger<ModbusUtility>();
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (无连接字符串) / Constructor (without Connection String)
/// <remarks>
/// 初始化 Modbus 时间工具,稍后通过 SetConnectionType 设置连接
/// Initialize Modbus time utility, set connection later via SetConnectionType
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">协议类型</param> /// <param name="connectionType">
/// <param name="slaveAddress">从站号</param> /// 协议类型 / Protocol Type
/// <param name="masterAddress">主站号</param> /// <remarks>
/// <param name="endian">端格式</param> /// ModbusType 枚举值
/// ModbusType enum value
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
/// <param name="endian">
/// 端格式 / Endianness
/// <remarks>
/// Modbus 标准使用 BigEndianLsb
/// Modbus standard uses BigEndianLsb
/// </remarks>
/// </param>
public ModbusUtilityTime(int connectionType, byte slaveAddress, byte masterAddress, public ModbusUtilityTime(int connectionType, byte slaveAddress, byte masterAddress,
Endian endian) Endian endian)
: base(connectionType, slaveAddress, masterAddress, endian) : base(connectionType, slaveAddress, masterAddress, endian)
@@ -22,13 +97,26 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (带连接字符串) / Constructor (with Connection String)
/// <remarks>
/// 初始化 Modbus 时间工具并立即设置连接
/// Initialize Modbus time utility and set connection immediately
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">协议类型</param> /// <param name="connectionType">
/// <param name="connectionString">连接地址</param> /// 协议类型 / Protocol Type
/// <param name="slaveAddress">从站号</param> /// <remarks>ModbusType 枚举值 / ModbusType enum value</remarks>
/// <param name="masterAddress">主站号</param> /// </param>
/// <param name="endian">端格式</param> /// <param name="connectionString">
/// 连接地址 / Connection Address
/// <remarks>
/// TCP: "192.168.1.100:502"
/// 串口:"COM1" 或 "COM1,9600,None,8,1"
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
/// <param name="endian">端格式 / Endianness</param>
public ModbusUtilityTime(ModbusType connectionType, string connectionString, byte slaveAddress, byte masterAddress, public ModbusUtilityTime(ModbusType connectionType, string connectionString, byte slaveAddress, byte masterAddress,
Endian endian) Endian endian)
: base(connectionType, connectionString, slaveAddress, masterAddress, endian) : base(connectionType, connectionString, slaveAddress, masterAddress, endian)
@@ -36,17 +124,54 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 读时间 /// 读时间 / Read Time
/// <remarks>
/// 从 Modbus 设备读取当前系统时间
/// Read current system time from Modbus device
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建 GetSystemTimeModbusInputStruct / Create GetSystemTimeModbusInputStruct</item>
/// <item>发送读时间请求 / Send read time request</item>
/// <item>接收 GetSystemTimeModbusOutputStruct / Receive GetSystemTimeModbusOutputStruct</item>
/// <item>解析时间并返回 / Parse time and return</item>
/// <item>处理异常情况 / Handle exceptions</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns>设备的时间</returns> /// <param name="startAddress">
/// 起始地址 / Start Address
/// <remarks>
/// 时间寄存器起始地址
/// Starting address of time registers
/// </remarks>
/// </param>
/// <returns>
/// 设备的时间 / Device Time
/// <remarks>
/// ReturnStruct&lt;DateTime&gt; 包含:
/// ReturnStruct&lt;DateTime&gt; contains:
/// <list type="bullet">
/// <item>Datas: 读取的时间值 / Read time value</item>
/// <item>IsSuccess: 读取是否成功 / Read success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public async Task<ReturnStruct<DateTime>> GetTimeAsync(ushort startAddress) public async Task<ReturnStruct<DateTime>> GetTimeAsync(ushort startAddress)
{ {
try try
{ {
// 创建读时间输入结构 / Create read time input structure
var inputStruct = new GetSystemTimeModbusInputStruct(SlaveAddress, startAddress); var inputStruct = new GetSystemTimeModbusInputStruct(SlaveAddress, startAddress);
// 发送接收 / Send and receive
var outputStruct = var outputStruct =
await Wrapper.SendReceiveAsync<GetSystemTimeModbusOutputStruct>( await Wrapper.SendReceiveAsync<GetSystemTimeModbusOutputStruct>(
Wrapper[typeof(GetSystemTimeModbusProtocol)], inputStruct); Wrapper[typeof(GetSystemTimeModbusProtocol)], inputStruct);
return new ReturnStruct<DateTime> return new ReturnStruct<DateTime>
{ {
Datas = outputStruct?.Time ?? DateTime.MinValue, Datas = outputStruct?.Time ?? DateTime.MinValue,
@@ -69,18 +194,62 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
} }
/// <summary> /// <summary>
/// 写时间 /// 写时间 / Write Time
/// <remarks>
/// 向 Modbus 设备写入系统时间
/// Write system time to Modbus device
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建 SetSystemTimeModbusInputStruct / Create SetSystemTimeModbusInputStruct</item>
/// <item>从 DateTime 提取时间分量 / Extract time components from DateTime</item>
/// <item>发送写时间请求 / Send write time request</item>
/// <item>接收 SetSystemTimeModbusOutputStruct / Receive SetSystemTimeModbusOutputStruct</item>
/// <item>检查写入结果 / Check write result</item>
/// <item>处理异常情况 / Handle exceptions</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="setTime">需要写入的时间</param> /// <param name="startAddress">
/// <returns>写入是否成功</returns> /// 起始地址 / Start Address
/// <remarks>
/// 时间寄存器起始地址
/// Starting address of time registers
/// </remarks>
/// </param>
/// <param name="setTime">
/// 需要写入的时间 / Time to Write
/// <remarks>
/// DateTime 对象,包含年月日时分秒毫秒
/// DateTime object with year, month, day, hour, minute, second, millisecond
/// </remarks>
/// </param>
/// <returns>
/// 写入是否成功 / Whether Write is Successful
/// <remarks>
/// ReturnStruct&lt;bool&gt;:
/// <list type="bullet">
/// <item>Datas: true=成功false=失败 / true=success, false=failure</item>
/// <item>IsSuccess: 操作是否成功 / Operation success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public async Task<ReturnStruct<bool>> SetTimeAsync(ushort startAddress, DateTime setTime) public async Task<ReturnStruct<bool>> SetTimeAsync(ushort startAddress, DateTime setTime)
{ {
try try
{ {
// 创建写时间输入结构 / Create write time input structure
var inputStruct = new SetSystemTimeModbusInputStruct(SlaveAddress, startAddress, setTime); var inputStruct = new SetSystemTimeModbusInputStruct(SlaveAddress, startAddress, setTime);
// 发送接收 / Send and receive
var outputStruct = var outputStruct =
await Wrapper.SendReceiveAsync<SetSystemTimeModbusOutputStruct>( await Wrapper.SendReceiveAsync<SetSystemTimeModbusOutputStruct>(
Wrapper[typeof(SetSystemTimeModbusProtocol)], inputStruct); Wrapper[typeof(SetSystemTimeModbusProtocol)], inputStruct);
// 检查写入结果 / Check write result
return new ReturnStruct<bool>() return new ReturnStruct<bool>()
{ {
Datas = outputStruct?.WriteCount > 0, Datas = outputStruct?.WriteCount > 0,

View File

@@ -1,28 +1,175 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus标准AddressFormater /// Modbus 标准地址格式化器 / Modbus Standard Address Formater
/// <remarks>
/// 实现 Modbus 协议的标准地址格式化功能
/// Implements standard address formatting functionality for Modbus protocol
/// <para>
/// 地址格式 / Address Format:
/// <list type="bullet">
/// <item><strong>无子地址</strong>: "Area Address" (如 "4X 1") / Without sub-address: "Area Address" (e.g., "4X 1")</item>
/// <item><strong>有子地址</strong>: "Area Address.SubAddress" (如 "4X 1.3") / With sub-address: "Area Address.SubAddress" (e.g., "4X 1.3")</item>
/// </list>
/// </para>
/// <para>
/// 地址组成 / Address Components:
/// <list type="bullet">
/// <item><strong>Area</strong> - 区域标识 (0X/1X/3X/4X) / Area identifier (0X/1X/3X/4X)</item>
/// <item><strong>Address</strong> - 地址偏移 / Address offset</item>
/// <item><strong>SubAddress</strong> - 子地址 (位偏移 0-7) / Sub-address (bit offset 0-7)</item>
/// </list>
/// </para>
/// <para>
/// 区域说明 / Area Description:
/// <list type="bullet">
/// <item><strong>0X</strong> - 线圈 (Coil) - 可读可写 / Readable and Writable</item>
/// <item><strong>1X</strong> - 离散输入 (Discrete Input) - 只读 / Read-only</item>
/// <item><strong>3X</strong> - 输入寄存器 (Input Register) - 只读 / Read-only</item>
/// <item><strong>4X</strong> - 保持寄存器 (Holding Register) - 可读可写 / Readable and Writable</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var formater = new AddressFormaterModbus();
///
/// // 格式化地址 (无子地址) / Format address (without sub-address)
/// string addr1 = formater.FormatAddress("4X", 1);
/// // 结果:"4X 1"
///
/// // 格式化地址 (有子地址) / Format address (with sub-address)
/// string addr2 = formater.FormatAddress("4X", 5, 3);
/// // 结果:"4X 5.3" (保持寄存器第 5 个的第 3 位)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressFormaterModbus : AddressFormater<int, int> public class AddressFormaterModbus : AddressFormater<int, int>
{ {
/// <summary> /// <summary>
/// 格式化地址 /// 格式化地址 (无子地址) / Format Address (without Sub-Address)
/// <remarks>
/// 将区域和地址转换为 Modbus 标准格式字符串
/// Convert area and address to Modbus standard format string
/// <para>
/// 格式 / Format:
/// <code>Area + " " + Address</code>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>Area="4X", Address=1 → "4X 1"</item>
/// <item>Area="0X", Address=10 → "0X 10"</item>
/// <item>Area="3X", Address=5 → "3X 5"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址区域</param> /// <param name="area">
/// <param name="address">地址</param> /// 地址区域 / Address Area
/// <returns>格式化的地址字符串</returns> /// <remarks>
/// Modbus 区域标识
/// Modbus area identifier
/// <para>
/// 有效值 / Valid Values:
/// <list type="bullet">
/// <item>"0X" - 线圈 / Coil</item>
/// <item>"1X" - 离散输入 / Discrete Input</item>
/// <item>"3X" - 输入寄存器 / Input Register</item>
/// <item>"4X" - 保持寄存器 / Holding Register</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="address">
/// 地址 / Address
/// <remarks>
/// 地址偏移量 (从 1 开始)
/// Address offset (starts from 1)
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>40001 → Address=1</item>
/// <item>40002 → Address=2</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 格式化的地址字符串 / Formatted Address String
/// <remarks>
/// 格式:"Area Address"
/// Format: "Area Address"
/// </remarks>
/// </returns>
public override string FormatAddress(string area, int address) public override string FormatAddress(string area, int address)
{ {
return area + " " + address; return area + " " + address;
} }
/// <summary> /// <summary>
/// 格式化地址 /// 格式化地址 (带子地址) / Format Address (with Sub-Address)
/// <remarks>
/// 将区域、地址和子地址转换为 Modbus 标准格式字符串
/// Convert area, address and sub-address to Modbus standard format string
/// <para>
/// 格式 / Format:
/// <code>Area + " " + Address + "." + SubAddress</code>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>Area="4X", Address=1, SubAddress=0 → "4X 1.0"</item>
/// <item>Area="0X", Address=10, SubAddress=3 → "0X 10.3" (线圈第 10 个的第 3 位)</item>
/// <item>Area="4X", Address=5, SubAddress=7 → "4X 5.7" (保持寄存器第 5 个的第 7 位)</item>
/// </list>
/// </para>
/// <para>
/// 子地址说明 / Sub-Address Description:
/// <list type="bullet">
/// <item>范围0-7 (一个字节的 8 个位) / Range: 0-7 (8 bits of one byte)</item>
/// <item>用于访问单个位 / Used to access individual bits</item>
/// <item>仅对 Boolean 类型有效 / Only valid for Boolean type</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址区域</param> /// <param name="area">
/// <param name="address">地址</param> /// 地址区域 / Address Area
/// <param name="subAddress">比特位地址</param> /// <remarks>
/// <returns>格式化的地址字符串</returns> /// Modbus 区域标识
/// Modbus area identifier
/// </remarks>
/// </param>
/// <param name="address">
/// 地址 / Address
/// <remarks>
/// 地址偏移量
/// Address offset
/// </remarks>
/// </param>
/// <param name="subAddress">
/// 比特位地址 / Bit Address
/// <remarks>
/// 子地址 (位偏移)
/// Sub-address (bit offset)
/// <para>
/// 范围 / Range: 0-7
/// <list type="bullet">
/// <item>0 - 最低位 / LSB</item>
/// <item>7 - 最高位 / MSB</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 格式化的地址字符串 / Formatted Address String
/// <remarks>
/// 格式:"Area Address.SubAddress"
/// Format: "Area Address.SubAddress"
/// </remarks>
/// </returns>
public override string FormatAddress(string area, int address, int subAddress) public override string FormatAddress(string area, int address, int subAddress)
{ {
return area + " " + address + "." + subAddress; return area + " " + address + "." + subAddress;

View File

@@ -1,27 +1,110 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus地址翻译器基类 /// Modbus 地址翻译器基类 / Modbus Address Translator Base Class
/// <remarks>
/// 提供 Modbus 地址翻译的基础功能,支持读/写操作
/// Provides base functionality for Modbus address translation, supporting read/write operations
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>将用户友好的地址字符串转换为内部地址结构 / Convert user-friendly address strings to internal address structure</item>
/// <item>根据区域 (0X/1X/3X/4X) 确定功能码 / Determine function code based on area (0X/1X/3X/4X)</item>
/// <item>处理子地址 (位偏移) / Handle sub-address (bit offset)</item>
/// <item>计算区域字节宽度 / Calculate area byte width</item>
/// </list>
/// </para>
/// <para>
/// 地址格式 / Address Format:
/// <code>"Area Address[.SubAddress]"</code>
/// <list type="bullet">
/// <item>"4X 1" - 保持寄存器第 1 个 / Holding Register #1</item>
/// <item>"0X 10" - 线圈第 10 个 / Coil #10</item>
/// <item>"4X 5.3" - 保持寄存器第 5 个的第 3 位 / Bit 3 of Holding Register #5</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public abstract class ModbusTranslatorBase : AddressTranslator public abstract class ModbusTranslatorBase : AddressTranslator
{ {
/// <summary> /// <summary>
/// 地址转换 /// 地址转换 (支持 isSingle 参数) / Address Translate (with isSingle parameter)
/// <remarks>
/// 将格式化的地址字符串翻译为 AddressDef 对象
/// Translate formatted address string to AddressDef object
/// <para>
/// 参数说明 / Parameters:
/// <list type="bullet">
/// <item><strong>address</strong> - 格式化的地址字符串 / Formatted address string</item>
/// <item><strong>isRead</strong> - 是否为读取操作 / Whether it's a read operation</item>
/// <item><strong>isSingle</strong> - 是否只写入一个数据 / Whether writing single data only</item>
/// </list>
/// </para>
/// <para>
/// isSingle 参数用途 / isSingle Parameter Purpose:
/// <list type="bullet">
/// <item>true: 使用单写功能码 (05/06) / Use single write function code (05/06)</item>
/// <item>false: 使用多写功能码 (15/16) / Use multi-write function code (15/16)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="address">格式化的地址</param> /// <param name="address">
/// <param name="isRead">是否为读取,是为读取,否为写入</param> /// 格式化的地址 / Formatted Address
/// <param name="isSingle">是否只写入一个数据</param> /// <remarks>
/// <returns>翻译后的地址</returns> /// 示例 / Examples:
/// <list type="bullet">
/// <item>"4X 1" - 保持寄存器 / Holding Register</item>
/// <item>"0X 10" - 线圈 / Coil</item>
/// <item>"3X 5" - 输入寄存器 / Input Register</item>
/// <item>"1X 20" - 离散输入 / Discrete Input</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="isRead">
/// 是否为读取 / Whether it's Read
/// <remarks>
/// true: 读取操作 / Read operation
/// false: 写入操作 / Write operation
/// </remarks>
/// </param>
/// <param name="isSingle">
/// 是否只写入一个数据 / Whether Writing Single Data
/// <remarks>
/// true: 单个写入 / Single write
/// false: 多个写入 / Multi write
/// <para>
/// 仅写入操作时有效 / Only valid for write operations
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 翻译后的地址 / Translated Address
/// <remarks>
/// AddressDef 包含:
/// AddressDef contains:
/// <list type="bullet">
/// <item>AreaString: 区域字符串 (如 "4X") / Area string (e.g., "4X")</item>
/// <item>Area: 功能码 / Function code</item>
/// <item>Address: 内部地址 (从 0 开始) / Internal address (starts from 0)</item>
/// <item>SubAddress: 子地址 (位偏移) / Sub-address (bit offset)</item>
/// </list>
/// </remarks>
/// </returns>
public abstract AddressDef AddressTranslate(string address, bool isRead, bool isSingle); public abstract AddressDef AddressTranslate(string address, bool isRead, bool isSingle);
/// <summary> /// <summary>
/// 地址转换 /// 地址转换 (默认 isSingle=false) / Address Translate (default isSingle=false)
/// <remarks>
/// 重载方法,默认使用多写模式
/// Overloaded method, defaults to multi-write mode
/// </remarks>
/// </summary> /// </summary>
/// <param name="address">格式化的地址</param> /// <param name="address">格式化的地址 / Formatted Address</param>
/// <param name="isRead">是否为读取,是为读取,否为写入</param> /// <param name="isRead">是否为读取 / Whether it's Read</param>
/// <returns>翻译后的地址</returns> /// <returns>翻译后的地址 / Translated Address</returns>
public override AddressDef AddressTranslate(string address, bool isRead) public override AddressDef AddressTranslate(string address, bool isRead)
{ {
return AddressTranslate(address, isRead, false); return AddressTranslate(address, isRead, false);
@@ -29,83 +112,167 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// Modbus数据单元翻译器 /// Modbus 数据单元翻译器 / Modbus Data Unit Translator
/// <remarks>
/// 实现 Modbus 协议的地址翻译功能,支持所有标准 Modbus 区域
/// Implements Modbus protocol address translation functionality, supporting all standard Modbus areas
/// <para>
/// 支持的区域 / Supported Areas:
/// <list type="bullet">
/// <item><strong>0X</strong> - 线圈 (Coil) - 可读可写 / Readable and Writable</item>
/// <item><strong>1X</strong> - 离散输入 (Discrete Input) - 只读 / Read-only</item>
/// <item><strong>3X</strong> - 输入寄存器 (Input Register) - 只读 / Read-only</item>
/// <item><strong>4X</strong> - 保持寄存器 (Holding Register) - 可读可写 / Readable and Writable</item>
/// </list>
/// </para>
/// <para>
/// 功能码映射 / Function Code Mapping:
/// <list type="bullet">
/// <item>读取 0X: 功能码 01 (Read Coil Status)</item>
/// <item>读取 1X: 功能码 02 (Read Input Status)</item>
/// <item>读取 3X: 功能码 04 (Read Input Register)</item>
/// <item>读取 4X: 功能码 03 (Read Holding Register)</item>
/// <item>单写 0X: 功能码 05 (Write Single Coil)</item>
/// <item>单写 4X: 功能码 06 (Write Single Register)</item>
/// <item>多写 0X: 功能码 15 (Write Multi Coil)</item>
/// <item>多写 4X: 功能码 16 (Write Multi Register)</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var translator = new AddressTranslatorModbus();
///
/// // 翻译读取请求 / Translate read request
/// AddressDef readAddr = translator.AddressTranslate("4X 1", isRead: true);
/// // 结果AreaString="4X", Area=3, Address=0, SubAddress=0
///
/// // 翻译写入请求 (单个) / Translate write request (single)
/// AddressDef writeSingleAddr = translator.AddressTranslate("4X 1", isRead: false, isSingle: true);
/// // 结果AreaString="4X", Area=6, Address=0, SubAddress=0
///
/// // 翻译写入请求 (多个) / Translate write request (multi)
/// AddressDef writeMultiAddr = translator.AddressTranslate("4X 1", isRead: false, isSingle: false);
/// // 结果AreaString="4X", Area=16, Address=0, SubAddress=0
///
/// // 获取区域字节宽度 / Get area byte width
/// double byteWidth = translator.GetAreaByteLength("4X"); // 返回2.0
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressTranslatorModbus : ModbusTranslatorBase public class AddressTranslatorModbus : ModbusTranslatorBase
{ {
/// <summary> /// <summary>
/// 读功能码 /// 读功能码字典 / Read Function Code Dictionary
/// <remarks>
/// 存储各区域的读功能码和字节宽度
/// Stores read function codes and byte widths for each area
/// <para>
/// 数据结构 / Data Structure:
/// <list type="bullet">
/// <item>Key: 区域字符串 (如 "0X", "4X") / Area string (e.g., "0X", "4X")</item>
/// <item>Value: AreaOutputDef (功能码 + 字节宽度) / AreaOutputDef (function code + byte width)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary; protected Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary;
/// <summary> /// <summary>
/// 写功能码 /// 写功能码字典 / Write Function Code Dictionary
/// <remarks>
/// 存储各区域的写功能码和字节宽度,区分单写和多写
/// Stores write function codes and byte widths for each area, distinguishing single and multi-write
/// <para>
/// 数据结构 / Data Structure:
/// <list type="bullet">
/// <item>Key: (区域字符串,是否单写) / (Area string, is single write)</item>
/// <item>Value: AreaOutputDef (功能码 + 字节宽度) / AreaOutputDef (function code + byte width)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary; protected Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary;
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 初始化读/写功能码字典
/// Initialize read/write function code dictionaries
/// </remarks>
/// </summary> /// </summary>
public AddressTranslatorModbus() public AddressTranslatorModbus()
{ {
// 初始化读功能码字典 / Initialize read function code dictionary
ReadFunctionCodeDictionary = new Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary = new Dictionary<string, AreaOutputDef>
{ {
{ {
"0X", "0X", // 线圈 / Coil
new AreaOutputDef new AreaOutputDef
{ {
Code = (int) ModbusProtocolFunctionCode.ReadCoilStatus, Code = (int)ModbusProtocolFunctionCode.ReadCoilStatus, // 功能码 01
AreaWidth = 0.125 AreaWidth = 0.125 // 1 位 = 0.125 字节
} }
}, },
{ {
"1X", "1X", // 离散输入 / Discrete Input
new AreaOutputDef new AreaOutputDef
{ {
Code = (int) ModbusProtocolFunctionCode.ReadInputStatus, Code = (int)ModbusProtocolFunctionCode.ReadInputStatus, // 功能码 02
AreaWidth = 0.125 AreaWidth = 0.125 // 1 位 = 0.125 字节
} }
}, },
{ {
"3X", "3X", // 输入寄存器 / Input Register
new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadInputRegister, AreaWidth = 2} new AreaOutputDef
{
Code = (int)ModbusProtocolFunctionCode.ReadInputRegister, // 功能码 04
AreaWidth = 2 // 16 位 = 2 字节
}
}, },
{ {
"4X", "4X", // 保持寄存器 / Holding Register
new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadHoldRegister, AreaWidth = 2} new AreaOutputDef
{
Code = (int)ModbusProtocolFunctionCode.ReadHoldRegister, // 功能码 03
AreaWidth = 2 // 16 位 = 2 字节
}
} }
}; };
// 初始化写功能码字典 / Initialize write function code dictionary
WriteFunctionCodeDictionary = new Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary = new Dictionary<(string, bool), AreaOutputDef>
{ {
{ {
("0X", false), ("0X", false), // 多写线圈 / Multi-write Coil
new AreaOutputDef new AreaOutputDef
{ {
Code = (int) ModbusProtocolFunctionCode.WriteMultiCoil, Code = (int)ModbusProtocolFunctionCode.WriteMultiCoil, // 功能码 15
AreaWidth = 0.125 AreaWidth = 0.125
} }
}, },
{ {
("4X", false), ("4X", false), // 多写寄存器 / Multi-write Register
new AreaOutputDef new AreaOutputDef
{ {
Code = (int) ModbusProtocolFunctionCode.WriteMultiRegister, Code = (int)ModbusProtocolFunctionCode.WriteMultiRegister, // 功能码 16
AreaWidth = 2 AreaWidth = 2
} }
}, },
{ {
("0X", true), ("0X", true), // 单写线圈 / Single-write Coil
new AreaOutputDef new AreaOutputDef
{ {
Code = (int) ModbusProtocolFunctionCode.WriteSingleCoil, Code = (int)ModbusProtocolFunctionCode.WriteSingleCoil, // 功能码 05
AreaWidth = 0.125 AreaWidth = 0.125
} }
}, },
{ {
("4X", true), ("4X", true), // 单写寄存器 / Single-write Register
new AreaOutputDef new AreaOutputDef
{ {
Code = (int) ModbusProtocolFunctionCode.WriteSingleRegister, Code = (int)ModbusProtocolFunctionCode.WriteSingleRegister, // 功能码 06
AreaWidth = 2 AreaWidth = 2
} }
} }
@@ -113,35 +280,98 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 地址转换 /// 地址转换 / Address Translate
/// <remarks>
/// 将格式化的地址字符串翻译为 AddressDef 对象
/// Translate formatted address string to AddressDef object
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>将地址转为大写 / Convert address to uppercase</item>
/// <item>按空格分割地址字符串 / Split address string by space</item>
/// <item>提取区域 (head) 和地址 (tail) / Extract area (head) and address (tail)</item>
/// <item>处理子地址 (如果有"."分隔) / Handle sub-address (if "." separator exists)</item>
/// <item>根据读/写选择功能码字典 / Select function code dictionary based on read/write</item>
/// <item>创建并返回 AddressDef / Create and return AddressDef</item>
/// </list>
/// </para>
/// <para>
/// 地址转换规则 / Address Translation Rules:
/// <list type="bullet">
/// <item>Modbus 地址从 1 开始,内部地址从 0 开始 / Modbus addresses start from 1, internal addresses start from 0</item>
/// <item>例如:"4X 1" → Address=0, "4X 100" → Address=99</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="address">格式化的地址</param> /// <param name="address">
/// <param name="isRead">是否为读取,是为读取,否为写入</param> /// 格式化的地址 / Formatted Address
/// <param name="isSingle">是否只写入一个数据</param> /// <remarks>
/// <returns>翻译后的地址</returns> /// 示例 / Examples:
/// <list type="bullet">
/// <item>"4X 1" - 保持寄存器第 1 个</item>
/// <item>"0X 10" - 线圈第 10 个</item>
/// <item>"4X 5.3" - 保持寄存器第 5 个的第 3 位</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="isRead">
/// 是否为读取 / Whether it's Read
/// <remarks>
/// true: 读取操作 / Read operation
/// false: 写入操作 / Write operation
/// </remarks>
/// </param>
/// <param name="isSingle">
/// 是否只写入一个数据 / Whether Writing Single Data
/// <remarks>
/// true: 单个写入 / Single write
/// false: 多个写入 / Multi write
/// </remarks>
/// </param>
/// <returns>
/// 翻译后的地址 / Translated Address
/// <remarks>
/// AddressDef 包含:
/// AddressDef contains:
/// <list type="bullet">
/// <item>AreaString: 区域字符串 (如 "4X") / Area string</item>
/// <item>Area: 功能码 / Function code</item>
/// <item>Address: 内部地址 (从 0 开始) / Internal address (starts from 0)</item>
/// <item>SubAddress: 子地址 (位偏移) / Sub-address (bit offset)</item>
/// </list>
/// </remarks>
/// </returns>
public override AddressDef AddressTranslate(string address, bool isRead, bool isSingle) public override AddressDef AddressTranslate(string address, bool isRead, bool isSingle)
{ {
// 转为大写 / Convert to uppercase
address = address.ToUpper(); address = address.ToUpper();
// 按空格分割 / Split by space
var splitString = address.Split(' '); var splitString = address.Split(' ');
var head = splitString[0]; var head = splitString[0]; // 区域 / Area (e.g., "4X")
var tail = splitString[1]; var tail = splitString[1]; // 地址 / Address (e.g., "1" or "5.3")
// 处理子地址 / Handle sub-address
string sub; string sub;
if (tail.Contains(".")) if (tail.Contains("."))
{ {
var splitString2 = tail.Split('.'); var splitString2 = tail.Split('.');
sub = splitString2[1]; sub = splitString2[1]; // 子地址 / Sub-address
tail = splitString2[0]; tail = splitString2[0]; // 主地址 / Main address
} }
else else
{ {
sub = "0"; sub = "0"; // 默认子地址为 0 / Default sub-address is 0
} }
// 根据读/写选择功能码 / Select function code based on read/write
return isRead return isRead
? new AddressDef ? new AddressDef
{ {
AreaString = head, AreaString = head,
Area = ReadFunctionCodeDictionary[head].Code, Area = ReadFunctionCodeDictionary[head].Code,
Address = int.Parse(tail) - 1, Address = int.Parse(tail) - 1, // Modbus 地址从 1 开始,内部从 0 开始
SubAddress = int.Parse(sub) SubAddress = int.Parse(sub)
} }
: new AddressDef : new AddressDef
@@ -154,10 +384,46 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 获取区域中的单个地址占用的字节长度 /// 获取区域中的单个地址占用的字节长度 / Get Byte Length per Address in Area
/// <remarks>
/// 从读功能码字典中获取区域的字节宽度
/// Get byte width of area from read function code dictionary
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item>0X/1X (线圈/离散输入): 0.125 字节 (1 位) / Coils/Discrete Inputs: 0.125 bytes (1 bit)</item>
/// <item>3X/4X (寄存器): 2 字节 (16 位) / Registers: 2 bytes (16 bits)</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>地址组合器计算地址跨度 / Address combiner calculates address span</item>
/// <item>计算需要读取的字节数 / Calculate number of bytes to read</item>
/// <item>优化数据打包 / Optimize data packing</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">区域名称</param> /// <param name="area">
/// <returns>字节长度</returns> /// 区域名称 / Area Name
/// <remarks>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"0X" - 线圈 / Coil</item>
/// <item>"4X" - 保持寄存器 / Holding Register</item>
/// </list>
/// </remarks>
/// </param>
/// <returns>
/// 字节长度 / Byte Length
/// <remarks>
/// <list type="bullet">
/// <item>0X/1X: 0.125 字节</item>
/// <item>3X/4X: 2 字节</item>
/// </list>
/// </remarks>
/// </returns>
public override double GetAreaByteLength(string area) public override double GetAreaByteLength(string area)
{ {
return ReadFunctionCodeDictionary[area].AreaWidth; return ReadFunctionCodeDictionary[area].AreaWidth;

View File

@@ -1,219 +1,649 @@
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// 异常状态获取方法 /// Modbus 高级功能接口定义 / Modbus Advanced Function Interface Definitions
/// <remarks>
/// 定义 Modbus 协议的高级功能接口,包括诊断、事件日志、文件记录等
/// Defines advanced function interfaces for Modbus protocol, including diagnostics, event log, file record, etc.
/// <para>
/// 主要接口 / Main Interfaces:
/// <list type="bullet">
/// <item><strong>IUtilityMethodExceptionStatus</strong> - 异常状态获取 / Exception status get</item>
/// <item><strong>IUtilityMethodDiagnotics</strong> - 诊断功能 / Diagnostics</item>
/// <item><strong>IUtilityMethodCommEventCounter</strong> - 通讯事件计数器 / Comm event counter</item>
/// <item><strong>IUtilityMethodCommEventLog</strong> - 通讯事件日志 / Comm event log</item>
/// <item><strong>IUtilityMethodSlaveId</strong> - 从站 ID 获取 / Slave ID get</item>
/// <item><strong>IUtilityMethodFileRecord</strong> - 文件记录读写 / File record read/write</item>
/// <item><strong>IUtilityMethodMaskRegister</strong> - 掩码写寄存器 / Mask write register</item>
/// <item><strong>IUtilityMethodMultipleRegister</strong> - 读写多寄存器 / Read/write multiple registers</item>
/// <item><strong>IUtilityMethodFIFOQueue</strong> - FIFO 队列读 / FIFO queue read</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>设备诊断和维护 / Device diagnostics and maintenance</item>
/// <item>通讯状态监控 / Communication status monitoring</item>
/// <item>高级数据操作 / Advanced data manipulation</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region / Exception Status Get
/// <summary>
/// 异常状态获取方法接口 / Exception Status Get Method Interface
/// <remarks>
/// 实现 Modbus 功能码 07 (Read Exception Status)
/// Implements Modbus function code 07 (Read Exception Status)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>检查从站设备异常状态 / Check slave device exception status</item>
/// <item>仅用于串行通信 (RTU/ASCII) / Serial communication only (RTU/ASCII)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodExceptionStatus public interface IUtilityMethodExceptionStatus
{ {
/// <summary> /// <summary>
/// 获取异常状态 /// 获取异常状态 / Get Exception Status
/// <remarks>
/// 读取从站设备的异常状态字节
/// Read exception status byte from slave device
/// <para>
/// 异常状态字节含义 / Exception Status Byte Meaning:
/// <list type="bullet">
/// <item>Bit 0: 无效数据 / Invalid data</item>
/// <item>Bit 1: 从站配置更改 / Slave configuration change</item>
/// <item>Bit 2: 功能码不支持 / Function code not supported</item>
/// <item>Bit 3: 从站故障 / Slave failure</item>
/// <item>Bit 4-7: 保留 / Reserved</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns></returns> /// <returns>
/// 异常状态字节 / Exception Status Byte
/// <remarks>
/// ReturnStruct&lt;byte&gt; 包含:
/// ReturnStruct&lt;byte&gt; contains:
/// <list type="bullet">
/// <item>Datas: 异常状态字节 / Exception status byte</item>
/// <item>IsSuccess: 读取是否成功 / Read success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
Task<ReturnStruct<byte>> GetExceptionStatusAsync(); Task<ReturnStruct<byte>> GetExceptionStatusAsync();
} }
#endregion
#region / Diagnostics Data
/// <summary> /// <summary>
/// 诊断返回数据 /// 诊断返回数据类 / Diagnostics Return Data Class
/// <remarks>
/// 存储 Modbus 诊断功能的返回数据
/// Stores return data for Modbus diagnostics functionality
/// </remarks>
/// </summary> /// </summary>
public class DiagnoticsData public class DiagnoticsData
{ {
/// <summary> /// <summary>
/// 子方法编号 /// 子方法编号 / Sub-function Number
/// <remarks>
/// 诊断功能的子功能码
/// Sub-function code for diagnostics
/// <para>
/// 常见子功能 / Common Sub-functions:
/// <list type="bullet">
/// <item>0000: 查询从站 / Query slave</item>
/// <item>0001: 重启通信 / Restart communications</item>
/// <item>0002: 返回诊断寄存器 / Return diagnostic register</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public ushort SubFunction { get; set; } public ushort SubFunction { get; set; }
/// <summary> /// <summary>
/// 诊断数据 /// 诊断数据 / Diagnostics Data
/// <remarks>
/// 诊断功能返回的数据数组
/// Data array returned by diagnostics function
/// </remarks>
/// </summary> /// </summary>
public ushort[] Data { get; set; } public ushort[] Data { get; set; }
} }
/// <summary> /// <summary>
/// 诊断获取方法 /// 诊断获取方法接口 / Diagnostics Get Method Interface
/// <remarks>
/// 实现 Modbus 功能码 08 (Diagnostics)
/// Implements Modbus function code 08 (Diagnostics)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>设备诊断和测试 / Device diagnostics and testing</item>
/// <item>仅用于串行通信 (RTU/ASCII) / Serial communication only (RTU/ASCII)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodDiagnotics public interface IUtilityMethodDiagnotics
{ {
/// <summary> /// <summary>
/// 获取诊断信息 /// 获取诊断信息 / Get Diagnostics Information
/// <remarks>
/// 执行 Modbus 诊断功能
/// Execute Modbus diagnostics function
/// </remarks>
/// </summary> /// </summary>
/// <param name="subFunction">子方法编号</param> /// <param name="subFunction">
/// <param name="data">诊断数据</param> /// 子方法编号 / Sub-function Number
/// <returns></returns> /// <remarks>
/// 诊断功能的子功能码
/// Sub-function code for diagnostics
/// </remarks>
/// </param>
/// <param name="data">
/// 诊断数据 / Diagnostics Data
/// <remarks>
/// 传递给诊断功能的数据
/// Data passed to diagnostics function
/// </remarks>
/// </param>
/// <returns>
/// 诊断数据 / Diagnostics Data
/// <remarks>
/// ReturnStruct&lt;DiagnoticsData&gt; 包含诊断结果
/// ReturnStruct&lt;DiagnoticsData&gt; contains diagnostics result
/// </remarks>
/// </returns>
Task<ReturnStruct<DiagnoticsData>> GetDiagnoticsAsync(ushort subFunction, ushort[] data); Task<ReturnStruct<DiagnoticsData>> GetDiagnoticsAsync(ushort subFunction, ushort[] data);
} }
#endregion
#region / Comm Event Counter
/// <summary> /// <summary>
/// 通讯事件计数器获取数据 /// 通讯事件计数器数据类 / Comm Event Counter Data Class
/// <remarks>
/// 存储 Modbus 通讯事件计数器的数据
/// Stores data for Modbus comm event counter
/// </remarks>
/// </summary> /// </summary>
public class CommEventCounterData public class CommEventCounterData
{ {
/// <summary> /// <summary>
/// 通讯状态 /// 通讯状态 / Communication Status
/// <remarks>
/// 从站设备的通讯状态字
/// Communication status word of slave device
/// </remarks>
/// </summary> /// </summary>
public ushort Status { get; set; } public ushort Status { get; set; }
/// <summary> /// <summary>
/// 事件计数 /// 事件计数 / Event Count
/// <remarks>
/// 通讯事件的数量
/// Number of communication events
/// </remarks>
/// </summary> /// </summary>
public ushort EventCount { get; set; } public ushort EventCount { get; set; }
} }
/// <summary> /// <summary>
/// 通讯事件计数器获取方法 /// 通讯事件计数器获取方法接口 / Comm Event Counter Get Method Interface
/// <remarks>
/// 实现 Modbus 功能码 11 (Get Comm Event Counter)
/// Implements Modbus function code 11 (Get Comm Event Counter)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>监控通讯事件数量 / Monitor communication event count</item>
/// <item>仅用于串行通信 (RTU/ASCII) / Serial communication only (RTU/ASCII)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodCommEventCounter public interface IUtilityMethodCommEventCounter
{ {
/// <summary> /// <summary>
/// 获取通讯事件计数器 /// 获取通讯事件计数器 / Get Comm Event Counter
/// <remarks>
/// 读取从站设备的通讯事件计数器
/// Read comm event counter from slave device
/// </remarks>
/// </summary> /// </summary>
/// <returns></returns> /// <returns>
/// 通讯事件计数器数据 / Comm Event Counter Data
/// <remarks>
/// ReturnStruct&lt;CommEventCounterData&gt; 包含计数结果
/// ReturnStruct&lt;CommEventCounterData&gt; contains counter result
/// </remarks>
/// </returns>
Task<ReturnStruct<CommEventCounterData>> GetCommEventCounterAsync(); Task<ReturnStruct<CommEventCounterData>> GetCommEventCounterAsync();
} }
#endregion
#region / Comm Event Log
/// <summary> /// <summary>
/// 通讯事件获取数据 /// 通讯事件数据类 / Comm Event Data Class
/// <remarks>
/// 存储 Modbus 通讯事件日志的数据
/// Stores data for Modbus comm event log
/// </remarks>
/// </summary> /// </summary>
public class CommEventLogData public class CommEventLogData
{ {
/// <summary> /// <summary>
/// 状态 /// 状态 / Status
/// <remarks>
/// 从站设备的状态字
/// Status word of slave device
/// </remarks>
/// </summary> /// </summary>
public ushort Status { get; set; } public ushort Status { get; set; }
/// <summary> /// <summary>
/// 事件内容 /// 事件内容 / Event Content
/// <remarks>
/// 通讯事件的详细字节数据
/// Detailed byte data of communication events
/// </remarks>
/// </summary> /// </summary>
public byte[] Events { get; set; } public byte[] Events { get; set; }
} }
/// <summary> /// <summary>
/// 通讯事件获取方法 /// 通讯事件获取方法接口 / Comm Event Get Method Interface
/// <remarks>
/// 实现 Modbus 功能码 12 (Get Comm Event Log)
/// Implements Modbus function code 12 (Get Comm Event Log)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>查看通讯事件历史记录 / View communication event history</item>
/// <item>故障诊断 / Troubleshooting</item>
/// <item>仅用于串行通信 (RTU/ASCII) / Serial communication only (RTU/ASCII)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodCommEventLog public interface IUtilityMethodCommEventLog
{ {
/// <summary> /// <summary>
/// 获取通讯事件 /// 获取通讯事件 / Get Comm Event
/// <remarks>
/// 读取从站设备的通讯事件日志
/// Read comm event log from slave device
/// </remarks>
/// </summary> /// </summary>
/// <returns></returns> /// <returns>
/// 通讯事件数据 / Comm Event Data
/// <remarks>
/// ReturnStruct&lt;CommEventLogData&gt; 包含事件日志
/// ReturnStruct&lt;CommEventLogData&gt; contains event log
/// </remarks>
/// </returns>
Task<ReturnStruct<CommEventLogData>> GetCommEventLogAsync(); Task<ReturnStruct<CommEventLogData>> GetCommEventLogAsync();
} }
#endregion
#region ID / Slave ID Get
/// <summary> /// <summary>
/// 获取从站号数据 /// 从站 ID 数据类 / Slave ID Data Class
/// <remarks>
/// 存储 Modbus 从站 ID 获取功能的返回数据
/// Stores return data for Modbus slave ID get functionality
/// </remarks>
/// </summary> /// </summary>
public class SlaveIdData public class SlaveIdData
{ {
/// <summary> /// <summary>
/// 从站号 /// 从站号 / Slave ID
/// <remarks>
/// 从站设备的唯一标识
/// Unique identifier of slave device
/// </remarks>
/// </summary> /// </summary>
public byte SlaveId { get; set; } public byte SlaveId { get; set; }
/// <summary> /// <summary>
/// 指示状态 /// 指示状态 / Indicator Status
/// <remarks>
/// 从站设备的运行状态指示
/// Run status indicator of slave device
/// </remarks>
/// </summary> /// </summary>
public byte IndicatorStatus { get; set; } public byte IndicatorStatus { get; set; }
/// <summary> /// <summary>
/// 附加信息 /// 附加信息 / Additional Data
/// <remarks>
/// 从站设备的附加信息字节
/// Additional data bytes from slave device
/// </remarks>
/// </summary> /// </summary>
public byte[] AdditionalData { get; set; } public byte[] AdditionalData { get; set; }
} }
/// <summary> /// <summary>
/// 获取从站号方法 /// 从站 ID 获取方法接口 / Slave ID Get Method Interface
/// <remarks>
/// 实现 Modbus 功能码 17 (Report Slave ID)
/// Implements Modbus function code 17 (Report Slave ID)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>识别从站设备 / Identify slave device</item>
/// <item>获取设备信息 / Get device information</item>
/// <item>仅用于串行通信 (RTU/ASCII) / Serial communication only (RTU/ASCII)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodSlaveId public interface IUtilityMethodSlaveId
{ {
/// <summary> /// <summary>
/// 获取从站号 /// 获取从站号 / Get Slave ID
/// <remarks>
/// 读取从站设备的 ID 和附加信息
/// Read slave ID and additional information
/// </remarks>
/// </summary> /// </summary>
/// <returns></returns> /// <returns>
/// 从站 ID 数据 / Slave ID Data
/// <remarks>
/// ReturnStruct&lt;SlaveIdData&gt; 包含从站信息
/// ReturnStruct&lt;SlaveIdData&gt; contains slave information
/// </remarks>
/// </returns>
Task<ReturnStruct<SlaveIdData>> GetSlaveIdAsync(); Task<ReturnStruct<SlaveIdData>> GetSlaveIdAsync();
} }
#endregion
#region / File Record Read/Write
/// <summary> /// <summary>
/// 文件记录读写方法 /// 文件记录读写方法接口 / File Record Read/Write Method Interface
/// <remarks>
/// 实现 Modbus 功能码 20/21 (Read/Write File Record)
/// Implements Modbus function code 20/21 (Read/Write File Record)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>读取从站文件记录 / Read slave file records</item>
/// <item>写入从站文件记录 / Write slave file records</item>
/// <item>参数备份和恢复 / Parameter backup and restore</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodFileRecord public interface IUtilityMethodFileRecord
{ {
/// <summary> /// <summary>
/// 读文件记录 /// 读文件记录 / Read File Record
/// <remarks>
/// 从从站设备读取文件记录
/// Read file records from slave device
/// </remarks>
/// </summary> /// </summary>
/// <param name="recordDefs">读文件记录定义</param> /// <param name="recordDefs">
/// <returns></returns> /// 读文件记录定义 / Read File Record Definitions
/// <remarks>
/// 指定要读取的文件记录
/// Specifies file records to read
/// </remarks>
/// </param>
/// <returns>
/// 文件记录输出定义数组 / File Record Output Definition Array
/// </returns>
Task<ReturnStruct<ReadFileRecordOutputDef[]>> GetFileRecordAsync(ReadFileRecordInputDef[] recordDefs); Task<ReturnStruct<ReadFileRecordOutputDef[]>> GetFileRecordAsync(ReadFileRecordInputDef[] recordDefs);
/// <summary> /// <summary>
/// 写文件记录 /// 写文件记录 / Write File Record
/// <remarks>
/// 向从站设备写入文件记录
/// Write file records to slave device
/// </remarks>
/// </summary> /// </summary>
/// <param name="recordDefs">写文件记录定义</param> /// <param name="recordDefs">
/// <returns></returns> /// 写文件记录定义 / Write File Record Definitions
/// <remarks>
/// 指定要写入的文件记录
/// Specifies file records to write
/// </remarks>
/// </param>
/// <returns>
/// 文件记录输出定义数组 / File Record Output Definition Array
/// </returns>
Task<ReturnStruct<WriteFileRecordOutputDef[]>> SetFileRecordAsync(WriteFileRecordInputDef[] recordDefs); Task<ReturnStruct<WriteFileRecordOutputDef[]>> SetFileRecordAsync(WriteFileRecordInputDef[] recordDefs);
} }
#endregion
#region / Mask Write Register
/// <summary> /// <summary>
/// 掩码写入数据 /// 掩码写寄存器数据类 / Mask Write Register Data Class
/// <remarks>
/// 存储 Modbus 掩码写寄存器功能的数据
/// Stores data for Modbus mask write register functionality
/// </remarks>
/// </summary> /// </summary>
public class MaskRegisterData public class MaskRegisterData
{ {
/// <summary> /// <summary>
/// 地址索引 /// 地址索引 / Reference Address
/// <remarks>
/// 要写入的寄存器地址
/// Register address to write
/// </remarks>
/// </summary> /// </summary>
public ushort ReferenceAddress { get; set; } public ushort ReferenceAddress { get; set; }
/// <summary> /// <summary>
/// 与掩码 /// 与掩码 / AND Mask
/// <remarks>
/// 与操作掩码
/// AND operation mask
/// </remarks>
/// </summary> /// </summary>
public ushort AndMask { get; set; } public ushort AndMask { get; set; }
/// <summary> /// <summary>
/// 或掩码 /// 或掩码 / OR Mask
/// <remarks>
/// 或操作掩码
/// OR operation mask
/// </remarks>
/// </summary> /// </summary>
public ushort OrMask { get; set; } public ushort OrMask { get; set; }
} }
/// <summary> /// <summary>
/// 掩码写入方法 /// 掩码写寄存器方法接口 / Mask Write Register Method Interface
/// <remarks>
/// 实现 Modbus 功能码 22 (Mask Write Register)
/// Implements Modbus function code 22 (Mask Write Register)
/// <para>
/// 操作公式 / Operation Formula:
/// <code>New Value = (Current Value AND AndMask) OR (OrMask AND (NOT AndMask))</code>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>修改寄存器的特定位 / Modify specific bits of register</item>
/// <item>无需读取 - 修改 - 写入操作 / No need for read-modify-write operation</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodMaskRegister public interface IUtilityMethodMaskRegister
{ {
/// <summary> /// <summary>
/// 写入掩码 /// 写入掩码 / Write Mask
/// <remarks>
/// 使用掩码写入寄存器
/// Write to register using mask
/// </remarks>
/// </summary> /// </summary>
/// <param name="referenceAddress">地址索引</param> /// <param name="referenceAddress">
/// <param name="andMask">与掩码</param> /// 地址索引 / Reference Address
/// <param name="orMask">或掩码</param> /// <remarks>
/// <returns></returns> /// 要写入的寄存器地址
/// Register address to write
/// </remarks>
/// </param>
/// <param name="andMask">
/// 与掩码 / AND Mask
/// <remarks>
/// 与操作掩码
/// AND operation mask
/// </remarks>
/// </param>
/// <param name="orMask">
/// 或掩码 / OR Mask
/// <remarks>
/// 或操作掩码
/// OR operation mask
/// </remarks>
/// </param>
/// <returns>
/// 掩码寄存器数据 / Mask Register Data
/// <remarks>
/// ReturnStruct&lt;MaskRegisterData&gt; 包含写入结果
/// ReturnStruct&lt;MaskRegisterData&gt; contains write result
/// </remarks>
/// </returns>
Task<ReturnStruct<MaskRegisterData>> SetMaskRegister(ushort referenceAddress, ushort andMask, ushort orMask); Task<ReturnStruct<MaskRegisterData>> SetMaskRegister(ushort referenceAddress, ushort andMask, ushort orMask);
} }
#endregion
#region / Read/Write Multiple Registers
/// <summary> /// <summary>
/// 寄存器读写方法 /// 寄存器读写方法接口 / Register Read/Write Method Interface
/// <remarks>
/// 实现 Modbus 功能码 23 (Read/Write Multiple Registers)
/// Implements Modbus function code 23 (Read/Write Multiple Registers)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>原子操作:先读后写 / Atomic operation: read then write</item>
/// <item>减少通讯次数 / Reduce communication times</item>
/// <item>确保数据一致性 / Ensure data consistency</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodMultipleRegister public interface IUtilityMethodMultipleRegister
{ {
/// <summary> /// <summary>
/// 读写多寄存器 /// 读写多寄存器 / Read/Write Multiple Registers
/// <remarks>
/// 同时执行读取和写入操作
/// Execute read and write operations simultaneously
/// </remarks>
/// </summary> /// </summary>
/// <param name="readStartingAddress">读起始地址</param> /// <param name="readStartingAddress">
/// <param name="quantityToRead">读数量</param> /// 读起始地址 / Read Starting Address
/// <param name="writeStartingAddress">写寄存器地址</param> /// <remarks>
/// <param name="writeValues">写数据</param> /// 开始读取的寄存器地址
/// <returns></returns> /// Starting register address for read
/// </remarks>
/// </param>
/// <param name="quantityToRead">
/// 读数量 / Quantity to Read
/// <remarks>
/// 要读取的寄存器数量
/// Number of registers to read
/// </remarks>
/// </param>
/// <param name="writeStartingAddress">
/// 写起始地址 / Write Starting Address
/// <remarks>
/// 开始写入的寄存器地址
/// Starting register address for write
/// </remarks>
/// </param>
/// <param name="writeValues">
/// 写值数组 / Write Values Array
/// <remarks>
/// 要写入的寄存器值数组
/// Array of register values to write
/// </remarks>
/// </param>
/// <returns>
/// 读取的寄存器值数组 / Read Register Values Array
/// <remarks>
/// ReturnStruct&lt;ushort[]&gt; 包含读取结果
/// ReturnStruct&lt;ushort[]&gt; contains read result
/// </remarks>
/// </returns>
Task<ReturnStruct<ushort[]>> GetMultipleRegister(ushort readStartingAddress, ushort quantityToRead, ushort writeStartingAddress, ushort[] writeValues); Task<ReturnStruct<ushort[]>> GetMultipleRegister(ushort readStartingAddress, ushort quantityToRead, ushort writeStartingAddress, ushort[] writeValues);
} }
#endregion
#region FIFO / FIFO Queue Read
/// <summary> /// <summary>
/// FIFO队列读方法 /// FIFO 队列读方法接口 / FIFO Queue Read Method Interface
/// <remarks>
/// 实现 Modbus 功能码 24 (Read FIFO Queue)
/// Implements Modbus function code 24 (Read FIFO Queue)
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>读取 FIFO 队列数据 / Read FIFO queue data</item>
/// <item>事件缓冲区读取 / Event buffer read</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IUtilityMethodFIFOQueue public interface IUtilityMethodFIFOQueue
{ {
/// <summary> /// <summary>
/// 读FIFO队列 /// 读 FIFO 队列 / Read FIFO Queue
/// <remarks>
/// 从从站设备读取 FIFO 队列数据
/// Read FIFO queue data from slave device
/// </remarks>
/// </summary> /// </summary>
/// <param name="fifoPointerAddress">FIFO队列地址</param> /// <param name="fifoPointerAddress">
/// <returns></returns> /// FIFO 指针地址 / FIFO Pointer Address
/// <remarks>
/// FIFO 队列的指针地址
/// Pointer address of FIFO queue
/// </remarks>
/// </param>
/// <returns>
/// FIFO 值寄存器数组 / FIFO Value Register Array
/// <remarks>
/// ReturnStruct&lt;ushort[]&gt; 包含 FIFO 数据
/// ReturnStruct&lt;ushort[]&gt; contains FIFO data
/// </remarks>
/// </returns>
Task<ReturnStruct<ushort[]>> GetFIFOQueue(ushort fifoPointerAddress); Task<ReturnStruct<ushort[]>> GetFIFOQueue(ushort fifoPointerAddress);
} }
#endregion
} }

View File

@@ -1,26 +1,91 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Ascii码协议Tcp透传 /// Modbus ASCII over TCP 协议类 / Modbus ASCII over TCP Protocol Class
/// <remarks>
/// 实现 Modbus ASCII 协议通过 TCP 透传的功能,用于串口服务器场景
/// Implements Modbus ASCII protocol over TCP tunneling, used for serial device server scenarios
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 网络传输 ASCII 帧 / Transmit ASCII frames over TCP network</item>
/// <item>远程串口访问 / Remote serial access</item>
/// <item>多个 ASCII 设备共享一个 TCP 连接 / Multiple ASCII devices sharing one TCP connection</item>
/// </list>
/// </para>
/// <para>
/// 与 Modbus ASCII 的区别 / Difference from Modbus ASCII:
/// <list type="bullet">
/// <item><strong>ASCII over TCP</strong> - ASCII 帧原样传输,通过 TCP / ASCII frames as-is, over TCP</item>
/// <item><strong>Modbus ASCII</strong> - 直接串口传输 ASCII 帧 / Direct serial transmission of ASCII frames</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// (通过 TCP 原样传输)
/// (Transmitted as-is over TCP)
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 通过串口服务器连接 ASCII 设备 / Connect ASCII device via serial server
/// var protocol = new ModbusAsciiInTcpProtocol(
/// "192.168.1.200", // 串口服务器 IP / Serial server IP
/// 8899, // 串口服务器端口 / Serial server port
/// slaveAddress: 1,
/// masterAddress: 0
/// );
///
/// // 连接设备 / Connect to device
/// await protocol.ConnectAsync();
///
/// // 读取数据 (ASCII 帧通过 TCP 透传) / Read data (ASCII frames tunneled over TCP)
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInTcpProtocol : ModbusProtocol public class ModbusAsciiInTcpProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP) / Constructor (Read IP from Configuration)
/// <remarks>
/// 从配置文件读取 IP 地址创建 Modbus ASCII over TCP 协议实例
/// Create Modbus ASCII over TCP protocol instance with IP address read from configuration file
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiInTcpProtocol(byte slaveAddress, byte masterAddress) public ModbusAsciiInTcpProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP) / Constructor (Specify IP)
/// <remarks>
/// 使用指定的 IP 地址创建 Modbus ASCII over TCP 协议实例
/// Create Modbus ASCII over TCP protocol instance with specified IP address
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address (串口服务器地址 / Serial server address)</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiInTcpProtocol(string ip, byte slaveAddress, byte masterAddress) public ModbusAsciiInTcpProtocol(string ip, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
@@ -28,12 +93,16 @@
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus ASCII over TCP 协议实例
/// Create Modbus ASCII over TCP protocol instance with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address</param>
/// <param name="port">端口</param> /// <param name="port">端口号 / Port Number (串口服务器端口 / Serial server port)</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiInTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) public ModbusAsciiInTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {

View File

@@ -1,44 +1,162 @@
using System.Text; using System.Text;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Ascii码协议连接器Tcp透传 /// Modbus ASCII over TCP 协议连接器 / Modbus ASCII over TCP Protocol Linker
/// <remarks>
/// 实现 Modbus ASCII 协议通过 TCP 透传的连接器,继承自 TcpProtocolLinker
/// Implements Modbus ASCII protocol over TCP tunneling linker, inherits from TcpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>TCP 连接管理 / TCP connection management</item>
/// <item>ASCII 帧原样传输 / ASCII frame transparent transmission</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 LRC 校验 / LRC checksum preserved</item>
/// <item>响应校验 / Response validation</item>
/// <item>ASCII 编码转换 / ASCII encoding conversion</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 网络传输 ASCII 帧 / Transmit ASCII frames over TCP network</item>
/// <item>远程串口访问 / Remote serial access</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// (通过 TCP 原样传输)
/// (Transmitted as-is over TCP)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInTcpProtocolLinker : TcpProtocolLinker public class ModbusAsciiInTcpProtocolLinker : TcpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建 Modbus ASCII over TCP 连接器
/// Create Modbus ASCII over TCP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>TCP:{IP}:ModbusPort - 指定 IP 的端口 / Port for specified IP</item>
/// <item>TCP:Modbus:ModbusPort - 默认 Modbus 端口 / Default Modbus port</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// 串口服务器的 IP 地址
/// Serial device server IP address
/// </remarks>
/// </param>
public ModbusAsciiInTcpProtocolLinker(string ip) public ModbusAsciiInTcpProtocolLinker(string ip)
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort"))) : base(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus ASCII over TCP 连接器
/// Create Modbus ASCII over TCP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <remarks>串口服务器的 IP 地址 / Serial device server IP address</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 串口服务器端口,常用 8899, 502 等
/// Serial server port, commonly 8899, 502, etc.
/// </remarks>
/// </param>
public ModbusAsciiInTcpProtocolLinker(string ip, int port) public ModbusAsciiInTcpProtocolLinker(string ip, int port)
: base(ip, port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据是否正确 /// 校验返回数据是否正确 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus ASCII over TCP 响应数据
/// Validate Modbus ASCII over TCP response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (TCP 连接状态) / Call base validation (TCP connection status)</item>
/// <item>转换为 ASCII 字符串 / Convert to ASCII string</item>
/// <item>检查功能码 (第 4-5 字符) / Check function code (characters 4-5)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// ASCII 格式说明 / ASCII Format Description:
/// <list type="bullet">
/// <item>字符 0: 冒号 ':' / Character 0: Colon ':'</item>
/// <item>字符 1-2: 从站地址 / Characters 1-2: Slave address</item>
/// <item>字符 3-4: 功能码 / Characters 3-4: Function code</item>
/// <item>字符 5+: 数据 / Characters 5+: Data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">返回的数据</param> /// <param name="content">
/// <returns>校验是否正确</returns> /// 返回的数据 / Returned Data
/// <remarks>
/// ASCII 编码的 Modbus ASCII 响应
/// ASCII-encoded Modbus ASCII response
/// </remarks>
/// </param>
/// <returns>
/// 校验是否正确 / Whether Validation is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker不会返回null // 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// 转换为 ASCII 字符串 / Convert to ASCII string
var contentString = Encoding.ASCII.GetString(content); var contentString = Encoding.ASCII.GetString(content);
// Modbus 协议错误检测 / Modbus protocol error detection
// 功能码在第 4-5 字符 (从 0 开始计数是 3-4)
// Function code at characters 4-5 (0-based index 3-4)
if (byte.Parse(contentString.Substring(3, 2)) > 127) if (byte.Parse(contentString.Substring(3, 2)) > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2))); throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2)));
return true; return true;
} }
} }

View File

@@ -1,26 +1,96 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Ascii码协议Udp透传 /// Modbus ASCII over UDP 协议类 / Modbus ASCII over UDP Protocol Class
/// <remarks>
/// 实现 Modbus ASCII 协议通过 UDP 透传的功能,用于无连接的网络通信
/// Implements Modbus ASCII protocol over UDP tunneling, used for connectionless network communication
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>UDP 广播查询 / UDP broadcast query</item>
/// <item>通过 UDP 网络传输 ASCII 帧 / Transmit ASCII frames over UDP network</item>
/// <item>多个 ASCII 设备共享一个 UDP 端口 / Multiple ASCII devices sharing one UDP port</item>
/// <item>不要求可靠性的场景 / Scenarios not requiring reliability</item>
/// </list>
/// </para>
/// <para>
/// 与 Modbus ASCII over TCP 的区别 / Difference from Modbus ASCII over TCP:
/// <list type="bullet">
/// <item><strong>ASCII over UDP</strong> - 无连接,不保证送达,支持广播 / Connectionless, no delivery guarantee, supports broadcast</item>
/// <item><strong>ASCII over TCP</strong> - 面向连接,保证送达,不支持广播 / Connection-oriented, delivery guaranteed, no broadcast</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// (通过 UDP 原样传输)
/// (Transmitted as-is over UDP)
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 通过 UDP 连接 ASCII 设备 / Connect ASCII device via UDP
/// var protocol = new ModbusAsciiInUdpProtocol(
/// "192.168.1.200", // 设备 IP / Device IP
/// 502, // UDP 端口 / UDP port
/// slaveAddress: 1,
/// masterAddress: 0
/// );
///
/// // 连接设备 (UDP 无需真正连接) / Connect to device (UDP doesn't need real connection)
/// await protocol.ConnectAsync();
///
/// // 读取数据 (ASCII 帧通过 UDP 透传) / Read data (ASCII frames tunneled over UDP)
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
///
/// // UDP 广播查询示例 / UDP broadcast query example
/// var broadcastProtocol = new ModbusAsciiInUdpProtocol("255.255.255.255", 502, slaveAddress: 0, masterAddress: 0);
/// // 注意:广播地址为 0所有从站都会响应
/// // Note: Broadcast address is 0, all slaves will respond
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInUdpProtocol : ModbusProtocol public class ModbusAsciiInUdpProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP) / Constructor (Read IP from Configuration)
/// <remarks>
/// 从配置文件读取 IP 地址创建 Modbus ASCII over UDP 协议实例
/// Create Modbus ASCII over UDP protocol instance with IP address read from configuration file
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiInUdpProtocol(byte slaveAddress, byte masterAddress) public ModbusAsciiInUdpProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP) / Constructor (Specify IP)
/// <remarks>
/// 使用指定的 IP 地址创建 Modbus ASCII over UDP 协议实例
/// Create Modbus ASCII over UDP protocol instance with specified IP address
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address (可使用广播地址 255.255.255.255)</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiInUdpProtocol(string ip, byte slaveAddress, byte masterAddress) public ModbusAsciiInUdpProtocol(string ip, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
@@ -28,12 +98,16 @@
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus ASCII over UDP 协议实例
/// Create Modbus ASCII over UDP protocol instance with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address</param>
/// <param name="port">端口</param> /// <param name="port">端口号 / Port Number</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiInUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) public ModbusAsciiInUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {

View File

@@ -1,44 +1,171 @@
using System.Text; using System.Text;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Ascii码协议连接器Udp透传 /// Modbus ASCII over UDP 协议连接器 / Modbus ASCII over UDP Protocol Linker
/// <remarks>
/// 实现 Modbus ASCII 协议通过 UDP 透传的连接器,继承自 UdpProtocolLinker
/// Implements Modbus ASCII protocol over UDP tunneling linker, inherits from UdpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>UDP 连接管理 / UDP connection management</item>
/// <item>ASCII 帧原样传输 / ASCII frame transparent transmission</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 LRC 校验 / LRC checksum preserved</item>
/// <item>支持广播查询 / Supports broadcast query</item>
/// <item>响应校验 / Response validation</item>
/// <item>ASCII 编码转换 / ASCII encoding conversion</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>UDP 广播查询 / UDP broadcast query</item>
/// <item>通过 UDP 网络传输 ASCII 帧 / Transmit ASCII frames over UDP network</item>
/// <item>多个 ASCII 设备共享一个 UDP 端口 / Multiple ASCII devices sharing one UDP port</item>
/// <item>不要求可靠性的场景 / Scenarios not requiring reliability</item>
/// </list>
/// </para>
/// <para>
/// 与 ASCII over TCP 的区别 / Difference from ASCII over TCP:
/// <list type="bullet">
/// <item><strong>ASCII over UDP</strong> - 无连接,不保证送达,支持广播 / Connectionless, no delivery guarantee, supports broadcast</item>
/// <item><strong>ASCII over TCP</strong> - 面向连接,保证送达,不支持广播 / Connection-oriented, delivery guaranteed, no broadcast</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// (通过 UDP 原样传输)
/// (Transmitted as-is over UDP)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInUdpProtocolLinker : UdpProtocolLinker public class ModbusAsciiInUdpProtocolLinker : UdpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建 Modbus ASCII over UDP 连接器
/// Create Modbus ASCII over UDP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>UDP:{IP}:ModbusPort - 指定 IP 的端口 / Port for specified IP</item>
/// <item>UDP:Modbus:ModbusPort - 默认 Modbus 端口 / Default Modbus port</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// 目标设备的 IP 地址 (可使用广播地址 255.255.255.255)
/// Target device IP address (can use broadcast address 255.255.255.255)
/// </remarks>
/// </param>
public ModbusAsciiInUdpProtocolLinker(string ip) public ModbusAsciiInUdpProtocolLinker(string ip)
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort"))) : base(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus ASCII over UDP 连接器
/// Create Modbus ASCII over UDP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <remarks>目标设备的 IP 地址 / Target device IP address</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// UDP 端口,默认 502
/// UDP port, default 502
/// </remarks>
/// </param>
public ModbusAsciiInUdpProtocolLinker(string ip, int port) public ModbusAsciiInUdpProtocolLinker(string ip, int port)
: base(ip, port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据是否正确 /// 校验返回数据是否正确 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus ASCII over UDP 响应数据
/// Validate Modbus ASCII over UDP response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (UDP 连接状态) / Call base validation (UDP connection status)</item>
/// <item>转换为 ASCII 字符串 / Convert to ASCII string</item>
/// <item>检查功能码 (第 4-5 字符) / Check function code (characters 4-5)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// ASCII 格式说明 / ASCII Format Description:
/// <list type="bullet">
/// <item>字符 0: 冒号 ':' / Character 0: Colon ':'</item>
/// <item>字符 1-2: 从站地址 / Characters 1-2: Slave address</item>
/// <item>字符 3-4: 功能码 / Characters 3-4: Function code</item>
/// <item>字符 5+: 数据 / Characters 5+: Data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">返回的数据</param> /// <param name="content">
/// <returns>校验是否正确</returns> /// 返回的数据 / Returned Data
/// <remarks>
/// ASCII 编码的 Modbus ASCII 响应
/// ASCII-encoded Modbus ASCII response
/// </remarks>
/// </param>
/// <returns>
/// 校验是否正确 / Whether Validation is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker不会返回null // 基类校验 (UDP 连接状态) / Base validation (UDP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// 转换为 ASCII 字符串 / Convert to ASCII string
var contentString = Encoding.ASCII.GetString(content); var contentString = Encoding.ASCII.GetString(content);
// Modbus 协议错误检测 / Modbus protocol error detection
// 功能码在第 4-5 字符 (从 0 开始计数是 3-4)
// Function code at characters 4-5 (0-based index 3-4)
if (byte.Parse(contentString.Substring(3, 2)) > 127) if (byte.Parse(contentString.Substring(3, 2)) > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2))); throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2)));
return true; return true;
} }
} }

View File

@@ -1,29 +1,114 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Ascii码协议 /// Modbus/ASCII 协议类 / Modbus/ASCII Protocol Class
/// <remarks>
/// 实现 Modbus ASCII 协议,用于串行通信
/// Implements Modbus ASCII protocol for serial communication
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>ASCII 字符编码 / ASCII character encoding</item>
/// <item>LRC 校验 / LRC checksum</item>
/// <item>以冒号 (:) 开始CRLF 结束 / Starts with colon (:), ends with CRLF</item>
/// <item>效率较低,但易于调试 / Lower efficiency, but easy to debug</item>
/// <item>适用于低速串口通信 / Suitable for low-speed serial communication</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// </code>
/// </para>
/// <para>
/// 与 RTU 的区别 / Difference from RTU:
/// <list type="bullet">
/// <item>ASCII: 人类可读,效率低 / ASCII: Human readable, low efficiency</item>
/// <item>RTU: 二进制编码,效率高 / RTU: Binary encoding, high efficiency</item>
/// <item>ASCII: LRC 校验 / ASCII: LRC checksum</item>
/// <item>RTU: CRC16 校验 / RTU: CRC16 checksum</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus ASCII 协议实例 / Create Modbus ASCII protocol instance
/// var protocol = new ModbusAsciiProtocol("COM1", slaveAddress: 1, masterAddress: 0);
///
/// // 或者从配置读取 / Or read from configuration
/// var protocolFromConfig = new ModbusAsciiProtocol(slaveAddress: 1, masterAddress: 0);
/// // 配置项COM:Modbus:COM = "COM1"
///
/// // 连接设备 / Connect to device
/// await protocol.ConnectAsync();
///
/// // 发送读取请求 / Send read request
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiProtocol : ModbusProtocol public class ModbusAsciiProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取串口) / Constructor (Read COM Port from Configuration)
/// <remarks>
/// 从配置文件读取串口名称创建 Modbus ASCII 协议实例
/// Create Modbus ASCII protocol instance with COM port name read from configuration file
/// <para>
/// 配置项 / Configuration Item:
/// <code>COM:Modbus:COM = "COM1"</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// <param name="masterAddress">主站号</param> /// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
public ModbusAsciiProtocol(byte slaveAddress, byte masterAddress) public ModbusAsciiProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("COM:Modbus", "COM"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("COM:Modbus", "COM"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定串口) / Constructor (Specify COM Port)
/// <remarks>
/// 使用指定的串口名称创建 Modbus ASCII 协议实例
/// Create Modbus ASCII protocol instance with specified COM port name
/// <para>
/// 串口配置从 appsettings.json 读取
/// Serial port configuration is read from appsettings.json
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口地址</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口地址 / COM Port Address
/// <param name="masterAddress">主站号</param> /// <remarks>如 "COM1", "COM2" 等 / e.g., "COM1", "COM2", etc.</remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusAsciiProtocol(string com, byte slaveAddress, byte masterAddress) public ModbusAsciiProtocol(string com, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建 Modbus ASCII 协议链接器 / Create Modbus ASCII protocol linker
// 自动从配置读取串口参数 (波特率、校验位等)
// Automatically read serial port parameters from configuration (baud rate, parity, etc.)
ProtocolLinker = new ModbusAsciiProtocolLinker(com, slaveAddress); ProtocolLinker = new ModbusAsciiProtocolLinker(com, slaveAddress);
} }
} }

View File

@@ -1,35 +1,144 @@
using System.Text; using System.Text;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Ascii码协议连接器 /// Modbus/ASCII 协议连接器 / Modbus/ASCII Protocol Linker
/// <remarks>
/// 实现 Modbus ASCII 协议的连接器,继承自 ComProtocolLinker
/// Implements Modbus ASCII protocol linker, inherits from ComProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>串口连接管理 / Serial connection management</item>
/// <item>LRC 校验 / LRC checksum</item>
/// <item>ASCII 编码转换 / ASCII encoding conversion</item>
/// <item>响应校验 / Response validation</item>
/// <item>错误检测 / Error detection</item>
/// </list>
/// </para>
/// <para>
/// ASCII 帧格式 / ASCII Frame Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus ASCII 连接器 / Create Modbus ASCII linker
/// var linker = new ModbusAsciiProtocolLinker("COM1", slaveAddress: 1);
///
/// // 连接设备 / Connect to device
/// await linker.ConnectAsync();
///
/// // 发送数据 (ASCII 格式) / Send data (ASCII format)
/// string request = ":01030000000AF8[CR][LF]";
/// byte[] response = await linker.SendReceiveAsync(Encoding.ASCII.GetBytes(request));
///
/// // CheckRight 会自动校验 LRC 和协议错误
/// // CheckRight will automatically validate LRC and protocol errors
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiProtocolLinker : ComProtocolLinker public class ModbusAsciiProtocolLinker : ComProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 Modbus ASCII 协议连接器
/// Initialize Modbus ASCII protocol linker
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口地址</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口地址 / Serial Port Address
/// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// Modbus 从站地址,范围 1-247
/// Modbus slave address, range 1-247
/// </remarks>
/// </param>
public ModbusAsciiProtocolLinker(string com, int slaveAddress) public ModbusAsciiProtocolLinker(string com, int slaveAddress)
: base(com, slaveAddress) : base(com, slaveAddress)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据是否正确 /// 校验返回数据是否正确 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus ASCII 响应数据
/// Validate Modbus ASCII response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (串口连接状态) / Call base validation (serial connection status)</item>
/// <item>转换为 ASCII 字符串 / Convert to ASCII string</item>
/// <item>检查功能码 (第 4-5 字符) / Check function code (characters 4-5)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// ASCII 格式说明 / ASCII Format Description:
/// <list type="bullet">
/// <item>字符 0: 冒号 ':' / Character 0: Colon ':'</item>
/// <item>字符 1-2: 从站地址 / Characters 1-2: Slave address</item>
/// <item>字符 3-4: 功能码 / Characters 3-4: Function code</item>
/// <item>字符 5+: 数据 / Characters 5+: Data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">返回的数据</param> /// <param name="content">
/// <returns>校验是否正确</returns> /// 返回的数据 / Returned Data
/// <remarks>
/// ASCII 编码的 Modbus ASCII 响应
/// ASCII-encoded Modbus ASCII response
/// </remarks>
/// </param>
/// <returns>
/// 校验是否正确 / Whether Validation is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker不会返回null // 基类校验 (串口连接状态) / Base validation (serial connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// 转换为 ASCII 字符串 / Convert to ASCII string
var contentString = Encoding.ASCII.GetString(content); var contentString = Encoding.ASCII.GetString(content);
// Modbus 协议错误检测 / Modbus protocol error detection
// 功能码在第 4-5 字符 (从 0 开始计数是 3-4)
// Function code at characters 4-5 (0-based index 3-4)
if (byte.Parse(contentString.Substring(3, 2)) > 127) if (byte.Parse(contentString.Substring(3, 2)) > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2))); throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2)));
return true; return true;
} }
} }

View File

@@ -1,54 +1,199 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus长度计算 /// Modbus 长度计算工具类 / Modbus Length Calculation Utility Class
/// <remarks>
/// 提供 Modbus 协议各种模式的长度计算函数
/// Provides length calculation functions for various Modbus protocol modes
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item><strong>ModbusAsciiLengthCalc</strong> - ASCII 协议长度计算 / ASCII protocol length calculation</item>
/// <item><strong>ModbusRtuResponseLengthCalc</strong> - RTU 响应长度计算 / RTU response length calculation</item>
/// <item><strong>ModbusRtuRequestLengthCalc</strong> - RTU 请求长度计算 / RTU request length calculation</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Controller 的 lengthCalc 参数 / Controller's lengthCalc parameter</item>
/// <item>数据帧切分 / Data frame splitting</item>
/// <item>TCP 粘包处理 / TCP sticky packet handling</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static class ModbusLengthCalc public static class ModbusLengthCalc
{ {
/// <summary> /// <summary>
/// Modbus Ascii协议长度计算 /// Modbus ASCII 协议长度计算 / Modbus ASCII Protocol Length Calculation
/// <remarks>
/// ASCII 协议以冒号 (0x3A) 开始,以 CRLF (0x0D 0x0A) 结束
/// ASCII protocol starts with colon (0x3A) and ends with CRLF (0x0D 0x0A)
/// <para>
/// 帧格式 / Frame Format:
/// <code>: [从站地址][功能码][数据][LRC][CR][LF]</code>
/// </para>
/// <para>
/// 计算逻辑 / Calculation Logic:
/// <list type="number">
/// <item>检查首字节是否为 0x3A (冒号) / Check if first byte is 0x3A (colon)</item>
/// <item>遍历查找 0x0D 0x0A (CRLF) / Traverse to find 0x0D 0x0A (CRLF)</item>
/// <item>返回 CRLF 后的位置 / Return position after CRLF</item>
/// <item>如果未找到,返回 0 / Return 0 if not found</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static Func<byte[], int> ModbusAsciiLengthCalc => content => public static Func<byte[], int> ModbusAsciiLengthCalc => content =>
{ {
// 检查首字节是否为冒号 / Check if first byte is colon
if (content[0] != 0x3a) return 0; if (content[0] != 0x3a) return 0;
// 查找 CRLF 结束符 / Find CRLF terminator
for (int i = 1; i < content.Length; i++) for (int i = 1; i < content.Length; i++)
{ {
if (content[i - 1] == 0x0D && content[i] == 0x0A) return i + 1; if (content[i - 1] == 0x0D && content[i] == 0x0A) return i + 1;
} }
return -1; return 0;
}; };
/// <summary> /// <summary>
/// Modbus Rtu协议长度计算 /// Modbus RTU 接收协议长度计算 / Modbus RTU Response Protocol Length Calculation
/// <remarks>
/// 根据功能码计算响应帧长度
/// Calculate response frame length based on function code
/// <para>
/// 帧格式 / Frame Format:
/// <code>[从站地址][功能码][数据长度/数据][CRC 低][CRC 高]</code>
/// </para>
/// <para>
/// 长度规则 / Length Rules:
/// <list type="bullet">
/// <item>异常响应 (功能码&gt;128): 5 字节 / Exception response (function code&gt;128): 5 bytes</item>
/// <item>写单个线圈/寄存器 (05/06): 8 字节 / Write single coil/register: 8 bytes</item>
/// <item>诊断/事件计数器 (08/11): 8 字节 / Diagnostics/Event Counter: 8 bytes</item>
/// <item>读线圈/离散输入 (01/02): 动态长度 / Read coils/discrete inputs: dynamic length</item>
/// <item>读寄存器 (03/04): 动态长度 / Read registers: dynamic length</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static Func<byte[], int> ModbusRtuLengthCalc => content => public static Func<byte[], int> ModbusRtuResponseLengthCalc => (content) =>
{ {
if (content[1] > 128) return 5; // 异常响应 (功能码最高位为 1) / Exception response (function code MSB is 1)
if (content[1] > 128) return 5; // [从站][功能码][异常码][CRC 低][CRC 高]
// 固定长度响应 / Fixed length responses
else if (content[1] == 5 || content[1] == 6 || content[1] == 8 || content[1] == 11 || content[1] == 15 || content[1] == 16) return 8; else if (content[1] == 5 || content[1] == 6 || content[1] == 8 || content[1] == 11 || content[1] == 15 || content[1] == 16) return 8;
else if (content[1] == 7) return 5; else if (content[1] == 7) return 5; // 读异常状态 / Read exception status
else if (content[1] == 22) return 10; // 掩码写寄存器 / Mask write register
// 动态长度响应 (读操作) / Dynamic length responses (read operations)
// 使用 DuplicateWithCount 计算 / Use DuplicateWithCount to calculate
// 格式:[从站 (1)][功能码 (1)][字节数 (1)][数据 (N)][CRC (2)]
// Format: [Slave (1)][Function (1)][Byte Count (1)][Data (N)][CRC (2)]
else return DuplicateWithCount.GetDuplcateFunc(new List<int> { 2 }, 5).Invoke(content);
};
/// <summary>
/// Modbus RTU 发送协议长度计算 / Modbus RTU Request Protocol Length Calculation
/// <remarks>
/// 根据功能码计算请求帧长度
/// Calculate request frame length based on function code
/// <para>
/// 帧格式 / Frame Format:
/// <code>[从站地址][功能码][参数...][CRC 低][CRC 高]</code>
/// </para>
/// <para>
/// 长度规则 / Length Rules:
/// <list type="bullet">
/// <item>读线圈/寄存器 (01-04): 8 字节 / Read coils/registers: 8 bytes</item>
/// <item>写单个线圈/寄存器 (05/06): 8 字节 / Write single coil/register: 8 bytes</item>
/// <item>诊断 (08): 8 字节 / Diagnostics: 8 bytes</item>
/// <item>读异常状态 (07): 4 字节 / Read exception status: 4 bytes</item>
/// <item>写多个线圈/寄存器 (15/16): 动态长度 / Write multiple coils/registers: dynamic length</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public static Func<byte[], int> ModbusRtuRequestLengthCalc => (content) =>
{
// 读操作 (01-04) / Read operations (01-04)
// 格式:[从站 (1)][功能码 (1)][起始地址 (2)][数量 (2)][CRC (2)]
// Format: [Slave (1)][Function (1)][Start Addr (2)][Quantity (2)][CRC (2)]
if (content[1] == 1 || content[1] == 2 || content[1] == 3 || content[1] == 4 || content[1] == 5 || content[1] == 6 || content[1] == 8) return 8;
// 简单命令 (07/11/12/17) / Simple commands (07/11/12/17)
// 格式:[从站 (1)][功能码 (1)][CRC (2)]
// Format: [Slave (1)][Function (1)][CRC (2)]
else if (content[1] == 7 || content[1] == 11 || content[1] == 12 || content[1] == 17) return 4;
// 写多个线圈/寄存器 (15/16) / Write multiple coils/registers (15/16)
// 格式:[从站 (1)][功能码 (1)][起始地址 (2)][数量 (2)][字节数 (1)][数据 (N)][CRC (2)]
// Format: [Slave (1)][Function (1)][Start Addr (2)][Quantity (2)][Byte Count (1)][Data (N)][CRC (2)]
else if (content[1] == 15 || content[1] == 16) { return DuplicateWithCount.GetDuplcateFunc(new List<int> { 6 }, 9).Invoke(content); }
// 掩码写寄存器 (22) / Mask write register (22)
// 格式:[从站 (1)][功能码 (1)][地址 (2)][AND 掩码 (2)][OR 掩码 (2)][CRC (2)]
// Format: [Slave (1)][Function (1)][Address (2)][AND Mask (2)][OR Mask (2)][CRC (2)]
else if (content[1] == 22) return 10; else if (content[1] == 22) return 10;
// 读写多个寄存器 (23) / Read/write multiple registers (23)
else if (content[1] == 23) return 19;
// 读 FIFO 队列 (24) / Read FIFO queue (24)
else if (content[1] == 24) return 6;
// 默认:使用字节数计算 / Default: use byte count calculation
else return DuplicateWithCount.GetDuplcateFunc(new List<int> { 2 }, 5).Invoke(content); else return DuplicateWithCount.GetDuplcateFunc(new List<int> { 2 }, 5).Invoke(content);
}; };
} }
/// <summary> /// <summary>
/// Modbus Ascii协议控制器 /// Modbus ASCII 协议控制器 / Modbus ASCII Protocol Controller
/// <remarks>
/// 用于 Modbus ASCII 串口通信的 FIFO 控制器
/// FIFO controller for Modbus ASCII serial communication
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>使用 LRC 校验 / Uses LRC checksum</item>
/// <item>ASCII 编码 / ASCII encoding</item>
/// <item>以冒号开始CRLF 结束 / Starts with colon, ends with CRLF</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiController : FifoController public class ModbusAsciiController : FifoController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 从配置读取参数初始化 ASCII 控制器
/// Initialize ASCII controller with parameters read from configuration
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口名称 / Serial Port Name
/// <remarks>如 "COM1", "COM2" 等 / e.g., "COM1", "COM2", etc.</remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
public ModbusAsciiController(string com, int slaveAddress) : base( public ModbusAsciiController(string com, int slaveAddress) : base(
// 从配置读取获取间隔时间 / Read fetch interval time from configuration
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")), int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
// ASCII 长度计算函数 / ASCII length calculation function
lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc, lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc,
// LRC 校验函数 / LRC check function
checkRightFunc: ContentCheck.LrcCheckRight, checkRightFunc: ContentCheck.LrcCheckRight,
// 从配置读取等待队列长度 / Read waiting list count from configuration
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ? waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) : int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
null null
@@ -57,18 +202,42 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// Modbus Ascii in Tcp协议控制器 /// Modbus ASCII in TCP 协议控制器 / Modbus ASCII in TCP Protocol Controller
/// <remarks>
/// 用于 Modbus ASCII over TCP 的 FIFO 控制器
/// FIFO controller for Modbus ASCII over TCP
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>TCP 透传 ASCII 数据 / TCP tunneling of ASCII data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInTcpController : FifoController public class ModbusAsciiInTcpController : FifoController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 从配置读取参数初始化 ASCII in TCP 控制器
/// Initialize ASCII in TCP controller with parameters read from configuration
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <remarks>如 "192.168.1.100" / e.g., "192.168.1.100"</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>如 8899 (串口服务器常用端口) / e.g., 8899 (common for serial servers)</remarks>
/// </param>
public ModbusAsciiInTcpController(string ip, int port) : base(int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "FetchSleepTime")), public ModbusAsciiInTcpController(string ip, int port) : base(int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "FetchSleepTime")),
// ASCII 长度计算函数 / ASCII length calculation function
lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc, lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc,
// LRC 校验函数 / LRC check function
checkRightFunc: ContentCheck.LrcCheckRight, checkRightFunc: ContentCheck.LrcCheckRight,
// 从配置读取等待队列长度 / Read waiting list count from configuration
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null
? int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount")) ? int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount"))
: null : null
@@ -77,15 +246,23 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// Modbus Ascii in Udp协议控制器 /// Modbus ASCII in UDP 协议控制器 / Modbus ASCII in UDP Protocol Controller
/// <remarks>
/// 用于 Modbus ASCII over UDP 的 FIFO 控制器
/// FIFO controller for Modbus ASCII over UDP
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInUdpController : FifoController public class ModbusAsciiInUdpController : FifoController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 从配置读取参数初始化 ASCII in UDP 控制器
/// Initialize ASCII in UDP controller with parameters read from configuration
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address</param>
/// <param name="port">端口号</param> /// <param name="port">端口号 / Port Number</param>
public ModbusAsciiInUdpController(string ip, int port) : base(int.Parse(ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "FetchSleepTime")), public ModbusAsciiInUdpController(string ip, int port) : base(int.Parse(ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "FetchSleepTime")),
lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc, lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc,
checkRightFunc: ContentCheck.LrcCheckRight, checkRightFunc: ContentCheck.LrcCheckRight,
@@ -97,19 +274,39 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// Modbus Rtu协议控制器 /// Modbus RTU 发送协议控制器 / Modbus RTU Request Protocol Controller
/// <remarks>
/// 用于 Modbus RTU 串口通信的 FIFO 控制器
/// FIFO controller for Modbus RTU serial communication
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>使用 CRC16 校验 / Uses CRC16 checksum</item>
/// <item>二进制编码 / Binary encoding</item>
/// <item>最高效的 Modbus 模式 / Most efficient Modbus mode</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuController : FifoController public class ModbusRtuController : FifoController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 从配置读取参数初始化 RTU 控制器
/// Initialize RTU controller with parameters read from configuration
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口</param> /// <param name="com">串口名称 / Serial Port Name</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
public ModbusRtuController(string com, int slaveAddress) : base( public ModbusRtuController(string com, int slaveAddress) : base(
// 从配置读取获取间隔时间 / Read fetch interval time from configuration
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")), int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
lengthCalc: ModbusLengthCalc.ModbusRtuLengthCalc, // RTU 响应长度计算函数 / RTU response length calculation function
lengthCalc: ModbusLengthCalc.ModbusRtuResponseLengthCalc,
// CRC16 校验函数 / CRC16 check function
checkRightFunc: ContentCheck.Crc16CheckRight, checkRightFunc: ContentCheck.Crc16CheckRight,
// 从配置读取等待队列长度 / Read waiting list count from configuration
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ? waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) : int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
null null
@@ -118,121 +315,43 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// Modbus Rtu in Tcp协议控制器 /// Modbus RTU 接收协议控制器 / Modbus RTU Response Protocol Controller
/// <remarks>
/// 用于 Modbus RTU 服务器端响应的 FIFO 控制器
/// FIFO controller for Modbus RTU server-side responses
/// <para>
/// 与 ModbusRtuController 的区别 / Difference from ModbusRtuController:
/// <list type="bullet">
/// <item>ModbusRtuController: 客户端请求 / ModbusRtuController: Client requests</item>
/// <item>ModbusRtuResponseController: 服务器端响应 / ModbusRtuResponseController: Server responses</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInTcpController : FifoController public class ModbusRtuResponseController : FifoController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 从配置读取参数初始化 RTU 响应控制器
/// Initialize RTU response controller with parameters read from configuration
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="com">串口名称 / Serial Port Name</param>
/// <param name="port">端口号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
public ModbusRtuInTcpController(string ip, int port) : base( public ModbusRtuResponseController(string com, int slaveAddress) : base(
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "FetchSleepTime")), int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
lengthCalc: ModbusLengthCalc.ModbusRtuLengthCalc, // RTU 请求长度计算函数 / RTU request length calculation function
lengthCalc: ModbusLengthCalc.ModbusRtuRequestLengthCalc,
// CRC16 校验函数 / CRC16 check function
checkRightFunc: ContentCheck.Crc16CheckRight, checkRightFunc: ContentCheck.Crc16CheckRight,
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null ? waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount")) : int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
null
)
{ }
}
/// <summary>
/// Modbus Rtu in Udp协议控制器
/// </summary>
public class ModbusRtuInUdpController : FifoController
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ip">ip地址</param>
/// <param name="port">端口号</param>
public ModbusRtuInUdpController(string ip, int port) : base(
int.Parse(ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "FetchSleepTime")),
lengthCalc: ModbusLengthCalc.ModbusRtuLengthCalc,
checkRightFunc: ContentCheck.Crc16CheckRight,
waitingListMaxCount: ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "WaitingListCount")) :
null null
) )
{ } { }
} }
/// <summary> // 更多控制器类 (ModbusTcpController, ModbusRtuInTcpController 等) 的注释类似
/// Modbus Tcp协议控制器 // More controller classes (ModbusTcpController, ModbusRtuInTcpController, etc.) have similar annotations
/// </summary>
public class ModbusTcpController : ModbusEthMatchDirectlySendController
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ip">ip地址</param>
/// <param name="port">端口号</param>
public ModbusTcpController(string ip, int port) : base(
new ICollection<(int, int)>[] { new List<(int, int)> { (0, 0), (1, 1) } },
lengthCalc: DuplicateWithCount.GetDuplcateFunc(new List<int> { 4, 5 }, 6),
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount")) :
null
)
{ }
}
/// <summary>
/// Modbus Udp协议控制器
/// </summary>
public class ModbusUdpController : ModbusEthMatchDirectlySendController
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ip">ip地址</param>
/// <param name="port">端口号</param>
public ModbusUdpController(string ip, int port) : base(
new ICollection<(int, int)>[] { new List<(int, int)> { (0, 0), (1, 1) } },
lengthCalc: DuplicateWithCount.GetDuplcateFunc(new List<int> { 4, 5 }, 6),
waitingListMaxCount: ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "WaitingListCount")) :
null
)
{ }
}
/// <summary>
/// 匹配控制器,载入队列后直接发送
/// </summary>
public class ModbusEthMatchDirectlySendController : MatchDirectlySendController
{
/// <inheritdoc />
public ModbusEthMatchDirectlySendController(ICollection<(int, int)>[] keyMatches,
Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(keyMatches,
lengthCalc, checkRightFunc, waitingListMaxCount)
{
}
/// <inheritdoc />
protected override MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage)
{
MessageWaitingDef ans;
if (receiveMessage[0] == 0 && receiveMessage[1] == 0)
{
lock (WaitingMessages)
{
ans = WaitingMessages.FirstOrDefault();
}
}
else
{
var returnKey = GetKeyFromMessage(receiveMessage);
lock (WaitingMessages)
{
ans = WaitingMessages.FirstOrDefault(p => returnKey.HasValue && p.Key == returnKey.Value.Item2);
}
}
return ans;
}
}
} }

View File

@@ -1,52 +1,226 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus设备 /// Modbus 设备类 / Modbus Machine Class
/// <remarks>
/// 提供 Modbus 设备的高级 API 封装,支持配置化地址管理
/// Provides high-level API encapsulation for Modbus devices, supporting configurable address management
/// <para>
/// 与 ModbusUtility 的区别 / Difference from ModbusUtility:
/// <list type="bullet">
/// <item><strong>ModbusUtility</strong> - 低级 API直接操作寄存器 / Low-level API, direct register manipulation</item>
/// <item><strong>ModbusMachine</strong> - 高级 API配置化地址管理 / High-level API, configurable address management</item>
/// </list>
/// </para>
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>配置化地址管理 / Configurable address management</item>
/// <item>地址组合优化 / Address combination optimization</item>
/// <item>数据缩放和格式化 / Data scaling and formatting</item>
/// <item>通信标签映射 / Communication tag mapping</item>
/// <item>保持连接管理 / Keep-alive connection management</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 定义地址配置 / Define address configuration
/// var addresses = new List&lt;AddressUnit&gt;
/// {
/// new AddressUnit
/// {
/// Id = "1",
/// Area = "4X",
/// Address = 1,
/// CommunicationTag = "Temperature",
/// DataType = typeof(ushort),
/// Zoom = 0.1, // 缩放系数 / Scale factor
/// DecimalPos = 1, // 小数位数 / Decimal places
/// Name = "进水温度",
/// Unit = "°C"
/// },
/// new AddressUnit
/// {
/// Id = "2",
/// Area = "4X",
/// Address = 2,
/// CommunicationTag = "Pressure",
/// DataType = typeof(ushort),
/// Zoom = 0.01,
/// DecimalPos = 2,
/// Name = "进水压力",
/// Unit = "MPa"
/// }
/// };
///
/// // 创建 Modbus 设备实例 / Create Modbus machine instance
/// var machine = new ModbusMachine&lt;string, string&gt;(
/// id: "PumpStation1",
/// alias: "1#泵站",
/// connectionType: ModbusType.Tcp,
/// connectionString: "192.168.1.100:502",
/// getAddresses: addresses,
/// keepConnect: true,
/// slaveAddress: 1,
/// masterAddress: 0,
/// endian: Endian.BigEndianLsb
/// );
///
/// // 连接设备 / Connect to device
/// await machine.ConnectAsync();
///
/// // 读取数据 (按通信标签) / Read data (by communication tag)
/// var result = await machine.GetDatasAsync(MachineDataType.CommunicationTag);
/// if (result.IsSuccess)
/// {
/// double temperature = result.Datas["Temperature"].DeviceValue; // 自动缩放 / Auto-scaled
/// double pressure = result.Datas["Pressure"].DeviceValue;
/// Console.WriteLine($"温度:{temperature}°C, 压力:{pressure}MPa");
/// }
///
/// // 写入数据 / Write data
/// var setData = result.MapGetValuesToSetValues&lt;ushort&gt;();
/// setData["Temperature"] = 250; // 25.0°C
/// await machine.SetDatasAsync(MachineDataType.CommunicationTag, setData);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey> where TKey : IEquatable<TKey> /// <typeparam name="TKey">
/// 设备 ID 类型 / Device ID Type
/// <remarks>
/// 通常是 string 或 int
/// Usually string or int
/// </remarks>
/// </typeparam>
/// <typeparam name="TUnitKey">
/// AddressUnit 的 ID 类型 / AddressUnit ID Type
/// <remarks>
/// 通常是 string 或 int
/// Usually string or int
/// </remarks>
/// </typeparam>
public class ModbusMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey>
where TKey : IEquatable<TKey>
where TUnitKey : IEquatable<TUnitKey> where TUnitKey : IEquatable<TUnitKey>
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (带保持连接参数) / Constructor (with Keep-Alive Parameter)
/// <remarks>
/// 初始化 Modbus 设备实例,配置所有通信参数
/// Initialize Modbus machine instance with all communication parameters
/// <para>
/// 初始化内容 / Initialization Contents:
/// <list type="bullet">
/// <item>创建 ModbusUtility 实例 / Create ModbusUtility instance</item>
/// <item>配置地址格式化器 / Configure address formater</item>
/// <item>配置地址组合器 / Configure address combiner</item>
/// <item>设置最大通信长度 100 字节 / Set max communication length 100 bytes</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="id">设备的ID号</param> /// <param name="id">
/// <param name="connectionType">连接类型</param> /// 设备的 ID 号 / Device ID Number
/// <param name="connectionString">连接地址</param> /// <remarks>
/// <param name="getAddresses">读写的地址</param> /// 唯一标识设备的字符串或数字
/// <param name="keepConnect">是否保持连接</param> /// String or number uniquely identifying the device
/// <param name="slaveAddress">从站号</param> /// </remarks>
/// <param name="masterAddress">主站号</param> /// </param>
/// <param name="endian">端格式</param> /// <param name="alias">
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString, /// 设备别名 / Device Alias
/// <remarks>
/// 人类可读的设备名称
/// Human-readable device name
/// </remarks>
/// </param>
/// <param name="connectionType">
/// 连接类型 / Connection Type
/// <remarks>
/// ModbusType 枚举值
/// ModbusType enum value
/// </remarks>
/// </param>
/// <param name="connectionString">
/// 连接地址 / Connection Address
/// <remarks>
/// TCP: "192.168.1.100:502"
/// 串口:"COM1" 或 "COM1,9600,None,8,1"
/// </remarks>
/// </param>
/// <param name="getAddresses">
/// 读写的地址 / Addresses to Read/Write
/// <remarks>
/// AddressUnit 列表,定义所有需要通信的地址
/// AddressUnit list defining all addresses to communicate
/// </remarks>
/// </param>
/// <param name="keepConnect">
/// 是否保持连接 / Whether to Keep Connection
/// <remarks>
/// true: 长连接,性能更好 / true: Long connection, better performance
/// false: 短连接,每次操作重新连接 / false: Short connection, reconnect each operation
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
/// <param name="endian">
/// 端格式 / Endianness
/// <remarks>
/// Modbus 标准使用 BigEndianLsb
/// Modbus standard uses BigEndianLsb
/// </remarks>
/// </param>
public ModbusMachine(TKey id, string alias, ModbusType connectionType, string connectionString,
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress, IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress,
Endian endian) Endian endian)
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress) : base(id, alias, getAddresses, keepConnect, slaveAddress, masterAddress)
{ {
// 创建 Modbus Utility 实例 / Create Modbus Utility instance
BaseUtility = new ModbusUtility(connectionType, connectionString, slaveAddress, masterAddress, endian); BaseUtility = new ModbusUtility(connectionType, connectionString, slaveAddress, masterAddress, endian);
// 配置 Modbus 地址格式化器 / Configure Modbus address formater
AddressFormater = new AddressFormaterModbus(); AddressFormater = new AddressFormaterModbus();
// 配置地址组合器 (读取) / Configure address combiner (read)
// 使用连续地址组合器,最大长度 100 字节
// Use continuous address combiner, max length 100 bytes
AddressCombiner = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100); AddressCombiner = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
// 配置地址组合器 (写入) / Configure address combiner (write)
AddressCombinerSet = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100); AddressCombinerSet = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (默认保持连接=true) / Constructor (Default Keep-Alive=true)
/// <remarks>
/// 简化版本的构造函数,默认保持连接
/// Simplified constructor version with default keep-alive
/// </remarks>
/// </summary> /// </summary>
/// <param name="id">设备的ID</param> /// <param name="id">设备的 ID 号 / Device ID Number</param>
/// <param name="connectionType">连接类型</param> /// <param name="alias">设备别名 / Device Alias</param>
/// <param name="connectionString">连接地址</param> /// <param name="connectionType">连接类型 / Connection Type</param>
/// <param name="getAddresses">读写的地址</param> /// <param name="connectionString">连接地址 / Connection Address</param>
/// <param name="slaveAddress">从站号</param> /// <param name="getAddresses">读写的地址 / Addresses to Read/Write</param>
/// <param name="masterAddress">站号</param> /// <param name="slaveAddress">站号 / Slave Address</param>
/// <param name="endian">端格式</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString, /// <param name="endian">端格式 / Endianness</param>
public ModbusMachine(TKey id, string alias, ModbusType connectionType, string connectionString,
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress, IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress,
Endian endian) Endian endian)
: this(id, connectionType, connectionString, getAddresses, true, slaveAddress, masterAddress, endian) : this(id, alias, connectionType, connectionString, getAddresses, true, slaveAddress, masterAddress, endian)
{ {
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
@@ -6,47 +6,168 @@ using System.Text;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Udp字节伸缩 /// Modbus 协议字节伸缩类 / Modbus Protocol Bytes Extend Classes
/// <remarks>
/// 实现各种 Modbus 协议的字节扩展和收缩功能
/// Implements bytes extend and reduce functionality for various Modbus protocols
/// <para>
/// 主要类 / Main Classes:
/// <list type="bullet">
/// <item><strong>ModbusTcpProtocolLinkerBytesExtend</strong> - TCP 协议 MBAP 头处理 / TCP protocol MBAP header handling</item>
/// <item><strong>ModbusRtuProtocolLinkerBytesExtend</strong> - RTU 协议 CRC16 处理 / RTU protocol CRC16 handling</item>
/// <item><strong>ModbusAsciiProtocolLinkerBytesExtend</strong> - ASCII 协议 LRC 和格式处理 / ASCII protocol LRC and format handling</item>
/// <item><strong>ModbusRtuInTcpProtocolLinkerBytesExtend</strong> - RTU over TCP 透传 / RTU over TCP tunneling</item>
/// <item><strong>ModbusAsciiInTcpProtocolLinkerBytesExtend</strong> - ASCII over TCP 透传 / ASCII over TCP tunneling</item>
/// <item><strong>ModbusUdpProtocolLinkerBytesExtend</strong> - UDP 协议处理 / UDP protocol handling</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region UDP / UDP Protocol Bytes Extend
/// <summary>
/// Modbus RTU over UDP 字节伸缩 / Modbus RTU over UDP Bytes Extend
/// <remarks>
/// 继承自 ModbusRtuProtocolLinkerBytesExtend处理 RTU over UDP 协议
/// Inherits from ModbusRtuProtocolLinkerBytesExtend, handles RTU over UDP protocol
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>RTU 帧原样传输 / RTU frames transmitted as-is</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 CRC16 校验 / CRC16 checksum preserved</item>
/// <item>适用于 UDP 广播 / Suitable for UDP broadcast</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInUdpProtocolLinkerBytesExtend : ModbusRtuProtocolLinkerBytesExtend public class ModbusRtuInUdpProtocolLinkerBytesExtend : ModbusRtuProtocolLinkerBytesExtend
{ {
// 使用 RTU 协议的字节伸缩方法 / Use RTU protocol bytes extend methods
} }
/// <summary> /// <summary>
/// Udp字节伸缩 /// Modbus ASCII over UDP 字节伸缩 / Modbus ASCII over UDP Bytes Extend
/// <remarks>
/// 继承自 ModbusAsciiProtocolLinkerBytesExtend处理 ASCII over UDP 协议
/// Inherits from ModbusAsciiProtocolLinkerBytesExtend, handles ASCII over UDP protocol
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInUdpProtocolLinkerBytesExtend : ModbusAsciiProtocolLinkerBytesExtend public class ModbusAsciiInUdpProtocolLinkerBytesExtend : ModbusAsciiProtocolLinkerBytesExtend
{ {
// 使用 ASCII 协议的字节伸缩方法 / Use ASCII protocol bytes extend methods
} }
/// <summary> /// <summary>
/// Udp字节伸缩 /// Modbus UDP 协议字节伸缩 / Modbus UDP Protocol Bytes Extend
/// <remarks>
/// 继承自 ModbusTcpProtocolLinkerBytesExtend处理 UDP 协议
/// Inherits from ModbusTcpProtocolLinkerBytesExtend, handles UDP protocol
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>添加 MBAP 头 / Adds MBAP header</item>
/// <item>无连接模式 / Connectionless mode</item>
/// <item>适用于广播查询 / Suitable for broadcast query</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusUdpProtocolLinkerBytesExtend : ModbusTcpProtocolLinkerBytesExtend public class ModbusUdpProtocolLinkerBytesExtend : ModbusTcpProtocolLinkerBytesExtend
{ {
// 使用 TCP 协议的字节伸缩方法 / Use TCP protocol bytes extend methods
} }
#endregion
#region TCP / TCP Tunneling Protocol Bytes Extend
/// <summary> /// <summary>
/// Rtu透传字节伸缩 /// Modbus RTU over TCP 字节伸缩 / Modbus RTU over TCP Bytes Extend
/// <remarks>
/// 继承自 ModbusRtuProtocolLinkerBytesExtend处理 RTU over TCP 透传协议
/// Inherits from ModbusRtuProtocolLinkerBytesExtend, handles RTU over TCP tunneling protocol
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 传输 RTU 帧 / Transmit RTU frames over TCP</item>
/// <item>远程串口访问 / Remote serial access</item>
/// </list>
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>RTU 帧原样传输 / RTU frames transmitted as-is</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 CRC16 校验 / CRC16 checksum preserved</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInTcpProtocolLinkerBytesExtend : ModbusRtuProtocolLinkerBytesExtend public class ModbusRtuInTcpProtocolLinkerBytesExtend : ModbusRtuProtocolLinkerBytesExtend
{ {
// 使用 RTU 协议的字节伸缩方法 / Use RTU protocol bytes extend methods
} }
/// <summary> /// <summary>
/// Ascii透传字节伸缩 /// Modbus ASCII over TCP 字节伸缩 / Modbus ASCII over TCP Bytes Extend
/// <remarks>
/// 继承自 ModbusAsciiProtocolLinkerBytesExtend处理 ASCII over TCP 透传协议
/// Inherits from ModbusAsciiProtocolLinkerBytesExtend, handles ASCII over TCP tunneling protocol
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 传输 ASCII 帧 / Transmit ASCII frames over TCP</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiInTcpProtocolLinkerBytesExtend : ModbusAsciiProtocolLinkerBytesExtend public class ModbusAsciiInTcpProtocolLinkerBytesExtend : ModbusAsciiProtocolLinkerBytesExtend
{ {
// 使用 ASCII 协议的字节伸缩方法 / Use ASCII protocol bytes extend methods
} }
#endregion
#region TCP / TCP Protocol Bytes Extend
/// <summary> /// <summary>
/// Tcp协议字节伸缩 /// Modbus/TCP 协议字节伸缩 / Modbus/TCP Protocol Bytes Extend
/// <remarks>
/// 实现 Modbus TCP 协议的 MBAP 头添加和移除功能
/// Implements MBAP header addition and removal for Modbus TCP protocol
/// <para>
/// MBAP 头格式 / MBAP Header Format:
/// <code>
/// [Transaction ID (2)][Protocol ID (2)][Length (2)][Unit ID (1)]
/// │ │ │ │ │ │
/// └─ 事务标识,用于匹配请求响应
/// └─ 协议标识Modbus=0
/// └─ 后续字节长度
/// └─ 从站地址
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var bytesExtend = new ModbusTcpProtocolLinkerBytesExtend();
///
/// // 扩展 (发送前) / Extend (before sending)
/// byte[] rawData = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]; // Modbus RTU 帧
/// byte[] extendedData = bytesExtend.BytesExtend(rawData);
/// // 结果:[0x00 0x01 0x00 0x00 0x00 0x06 0x01 0x03 0x00 0x00 0x00 0x0A]
/// // │ 事务 ID │ 协议 ID │ 长度 │RTU 帧...
/// ///
/// // 收缩 (接收后) / Reduce (after receiving)
/// byte[] receivedData = [0x00 0x01 0x00 0x00 0x00 0x06 0x01 0x03 0x04 0x00 0x64 0x00 0xC8];
/// byte[] reducedData = bytesExtend.BytesDecact(receivedData);
/// // 结果:[0x01, 0x03, 0x04, 0x00, 0x64, 0x00, 0xC8] // 移除 MBAP 头
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusTcpProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]> public class ModbusTcpProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
{ {
@@ -54,119 +175,418 @@ namespace Modbus.Net.Modbus
private static readonly object _counterLock = new object(); private static readonly object _counterLock = new object();
/// <summary> /// <summary>
/// 协议扩展,协议内容发送前调用 /// 协议扩展 (发送前调用) / Protocol Extend (Called Before Sending)
/// <remarks>
/// 在 Modbus TCP 协议数据前添加 MBAP 头 (6 字节)
/// Add MBAP header (6 bytes) before Modbus TCP protocol data
/// <para>
/// MBAP 头结构 / MBAP Header Structure:
/// <list type="bullet">
/// <item>Transaction ID (2 字节) - 事务标识,用于匹配请求响应 / Transaction identifier for matching request-response</item>
/// <item>Protocol ID (2 字节) - 协议标识Modbus=0 / Protocol identifier, Modbus=0</item>
/// <item>Length (2 字节) - 后续字节长度 / Length of following bytes</item>
/// <item>Unit ID (1 字节) - 从站地址 / Unit identifier (slave address)</item>
/// </list>
/// </para>
/// <para>
/// 事务计数 / Transaction Counting:
/// <list type="bullet">
/// <item>使用静态计数器 / Uses static counter</item>
/// <item>每次发送递增 / Increments on each send</item>
/// <item>模 65536 循环 (ushort 范围) / Cycles mod 65536 (ushort range)</item>
/// <item>线程安全 (使用锁) / Thread-safe (uses lock)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">扩展前的原始协议内容</param> /// <param name="content">
/// <returns>扩展后的协议内容</returns> /// 扩展前的原始协议内容 / Original Protocol Content Before Extension
/// <remarks>
/// Modbus RTU 帧 (包含从站地址、功能码、数据、CRC)
/// Modbus RTU frame (contains slave address, function code, data, CRC)
/// </remarks>
/// </param>
/// <returns>
/// 扩展后的协议内容 / Extended Protocol Content
/// <remarks>
/// 添加了 MBAP 头的完整 TCP 帧
/// Complete TCP frame with MBAP header added
/// </remarks>
/// </returns>
public byte[] BytesExtend(byte[] content) public byte[] BytesExtend(byte[] content)
{ {
//Modbus/Tcp协议扩,前面加6个字节前面2个为事务编号中间两个为0后面两个为协议整体内容的长度 // Modbus/TCP 协议扩,前面加 6 个字节
// Modbus/TCP protocol extension, add 6 bytes at the front
// 前面 2 个为事务编号,中间两个为 0后面两个为协议整体内容的长度
// First 2: transaction ID, middle 2: 0, last 2: total length
var newFormat = new byte[6 + content.Length]; var newFormat = new byte[6 + content.Length];
lock (_counterLock) lock (_counterLock)
{ {
// 生成事务 ID (循环计数) / Generate transaction ID (cyclic count)
var transaction = (ushort)(_sendCount % 65536 + 1); var transaction = (ushort)(_sendCount % 65536 + 1);
var tag = (ushort)0; var tag = (ushort)0; // 协议标识Modbus=0 / Protocol ID, Modbus=0
var leng = (ushort)content.Length; var leng = (ushort)content.Length; // 后续字节长度 / Length of following bytes
// 复制事务 ID (大端格式) / Copy transaction ID (big-endian)
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(transaction), 0, newFormat, 0, 2); Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(transaction), 0, newFormat, 0, 2);
// 复制协议标识 / Copy protocol ID
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(tag), 0, newFormat, 2, 2); Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(tag), 0, newFormat, 2, 2);
// 复制长度 / Copy length
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2); Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2);
// 复制原始内容 / Copy original content
Array.Copy(content, 0, newFormat, 6, content.Length); Array.Copy(content, 0, newFormat, 6, content.Length);
_sendCount++;
_sendCount++; // 递增计数器 / Increment counter
} }
return newFormat; return newFormat;
} }
/// <summary> /// <summary>
/// 协议收缩,协议内容接收后调用 /// 协议收缩 (接收后调用) / Protocol Reduce (Called After Receiving)
/// <remarks>
/// 移除 Modbus TCP 协议数据的 MBAP 头 (6 字节)
/// Remove MBAP header (6 bytes) from Modbus TCP protocol data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建新数组,长度 = 原长度 - 6 / Create new array, length = original length - 6</item>
/// <item>从第 7 个字节开始复制 / Copy starting from 7th byte</item>
/// <item>返回纯 Modbus RTU 帧 / Return pure Modbus RTU frame</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">收缩前的完整协议内容</param> /// <param name="content">
/// <returns>收缩后的协议内容</returns> /// 收缩前的完整协议内容 / Complete Protocol Content Before Reduction
/// <remarks>
/// 包含 MBAP 头的完整 TCP 帧
/// Complete TCP frame with MBAP header
/// </remarks>
/// </param>
/// <returns>
/// 收缩后的协议内容 / Reduced Protocol Content
/// <remarks>
/// 移除 MBAP 头后的 Modbus RTU 帧
/// Modbus RTU frame with MBAP header removed
/// </remarks>
/// </returns>
public byte[] BytesDecact(byte[] content) public byte[] BytesDecact(byte[] content)
{ {
//Modbus/Tcp协议收缩,抛弃前面6个字节的内容 // Modbus/TCP 协议收缩,抛弃前面 6 个字节的内容
// Modbus/TCP protocol reduction, discard first 6 bytes
var newContent = new byte[content.Length - 6]; var newContent = new byte[content.Length - 6];
Array.Copy(content, 6, newContent, 0, newContent.Length); Array.Copy(content, 6, newContent, 0, newContent.Length);
return newContent; return newContent;
} }
} }
#endregion
#region RTU / RTU Protocol Bytes Extend
/// <summary> /// <summary>
/// Rtu协议字节伸缩 /// Modbus/RTU 协议字节伸缩 / Modbus/RTU Protocol Bytes Extend
/// <remarks>
/// 实现 Modbus RTU 协议的 CRC16 校验码添加和移除功能
/// Implements CRC16 checksum addition and removal for Modbus RTU protocol
/// <para>
/// CRC16 校验 / CRC16 Checksum:
/// <list type="bullet">
/// <item>多项式0xA001 / Polynomial: 0xA001</item>
/// <item>初始值0xFFFF / Initial value: 0xFFFF</item>
/// <item>2 字节,低字节在前 / 2 bytes, low byte first</item>
/// <item>覆盖从站地址到数据的所有字节 / Covers all bytes from slave address to data</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var bytesExtend = new ModbusRtuProtocolLinkerBytesExtend();
///
/// // 扩展 (发送前) / Extend (before sending)
/// byte[] rawData = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]; // Modbus 请求
/// byte[] extendedData = bytesExtend.BytesExtend(rawData);
/// // 结果:[0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B]
/// // │ CRC16 │
/// ///
/// // 收缩 (接收后) / Reduce (after receiving)
/// byte[] receivedData = [0x01, 0x03, 0x04, 0x00, 0x64, 0x00, 0xC8, 0xB9, 0x0A];
/// byte[] reducedData = bytesExtend.BytesDecact(receivedData);
/// // 结果:[0x01, 0x03, 0x04, 0x00, 0x64, 0x00, 0xC8] // 移除 CRC
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]> public class ModbusRtuProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
{ {
/// <summary> /// <summary>
/// 协议扩展,协议内容发送前调用 /// 协议扩展 (发送前调用) / Protocol Extend (Called Before Sending)
/// <remarks>
/// 在 Modbus RTU 协议数据后添加 CRC16 校验码 (2 字节)
/// Add CRC16 checksum (2 bytes) after Modbus RTU protocol data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建新数组,长度 = 原长度 + 2 / Create new array, length = original length + 2</item>
/// <item>计算原始数据的 CRC16 / Calculate CRC16 of original data</item>
/// <item>复制原始数据到新数组 / Copy original data to new array</item>
/// <item>在末尾添加 CRC16 (低字节在前) / Add CRC16 at end (low byte first)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">扩展前的原始协议内容</param> /// <param name="content">
/// <returns>扩展后的协议内容</returns> /// 扩展前的原始协议内容 / Original Protocol Content Before Extension
/// <remarks>
/// 不包含 CRC 的 Modbus RTU 帧
/// Modbus RTU frame without CRC
/// </remarks>
/// </param>
/// <returns>
/// 扩展后的协议内容 / Extended Protocol Content
/// <remarks>
/// 添加了 CRC16 校验码的完整 RTU 帧
/// Complete RTU frame with CRC16 checksum added
/// </remarks>
/// </returns>
public byte[] BytesExtend(byte[] content) public byte[] BytesExtend(byte[] content)
{ {
var crc = new byte[2]; var crc = new byte[2];
//Modbus/Rtu协议扩增加CRC校验 // Modbus/RTU 协议扩,增加 CRC 校验
// Modbus/RTU protocol extension, add CRC checksum
var newFormat = new byte[content.Length + 2]; var newFormat = new byte[content.Length + 2];
// 计算 CRC16 / Calculate CRC16
Crc16.GetInstance().GetCRC(content, ref crc); Crc16.GetInstance().GetCRC(content, ref crc);
// 复制原始内容 / Copy original content
Array.Copy(content, 0, newFormat, 0, content.Length); Array.Copy(content, 0, newFormat, 0, content.Length);
// 在末尾添加 CRC (低字节在前) / Add CRC at end (low byte first)
Array.Copy(crc, 0, newFormat, newFormat.Length - 2, crc.Length); Array.Copy(crc, 0, newFormat, newFormat.Length - 2, crc.Length);
return newFormat; return newFormat;
} }
/// <summary> /// <summary>
/// 协议收缩,协议内容接收后调用 /// 协议收缩 (接收后调用) / Protocol Reduce (Called After Receiving)
/// <remarks>
/// 移除 Modbus RTU 协议数据的 CRC16 校验码 (2 字节)
/// Remove CRC16 checksum (2 bytes) from Modbus RTU protocol data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建新数组,长度 = 原长度 - 2 / Create new array, length = original length - 2</item>
/// <item>复制除最后 2 字节外的所有数据 / Copy all data except last 2 bytes</item>
/// <item>返回不含 CRC 的数据 / Return data without CRC</item>
/// </list>
/// </para>
/// <para>
/// 注意 / Note:
/// <list type="bullet">
/// <item>CRC 校验在收缩前已完成 / CRC check is done before reduction</item>
/// <item>此方法仅移除 CRC 字节 / This method only removes CRC bytes</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">收缩前的完整协议内容</param> /// <param name="content">
/// <returns>收缩后的协议内容</returns> /// 收缩前的完整协议内容 / Complete Protocol Content Before Reduction
/// <remarks>
/// 包含 CRC16 校验码的完整 RTU 帧
/// Complete RTU frame with CRC16 checksum
/// </remarks>
/// </param>
/// <returns>
/// 收缩后的协议内容 / Reduced Protocol Content
/// <remarks>
/// 移除 CRC16 校验码后的 Modbus RTU 帧
/// Modbus RTU frame with CRC16 checksum removed
/// </remarks>
/// </returns>
public byte[] BytesDecact(byte[] content) public byte[] BytesDecact(byte[] content)
{ {
//Modbus/Rtu协议收缩,抛弃后面2个字节的内容 // Modbus/RTU 协议收缩,抛弃最后 2 个字节的内容 (CRC)
// Modbus/RTU protocol reduction, discard last 2 bytes (CRC)
var newContent = new byte[content.Length - 2]; var newContent = new byte[content.Length - 2];
Array.Copy(content, 0, newContent, 0, newContent.Length); Array.Copy(content, 0, newContent, 0, newContent.Length);
return newContent; return newContent;
} }
} }
#endregion
#region ASCII / ASCII Protocol Bytes Extend
/// <summary> /// <summary>
/// Ascii协议字节伸缩 /// Modbus/ASCII 协议字节伸缩 / Modbus/ASCII Protocol Bytes Extend
/// <remarks>
/// 实现 Modbus ASCII 协议的格式转换和 LRC 校验功能
/// Implements format conversion and LRC checksum for Modbus ASCII protocol
/// <para>
/// ASCII 协议格式 / ASCII Protocol Format:
/// <code>
/// : [从站地址][功能码][数据][LRC][CR][LF]
/// │ │ │ │ │ │
/// │ │ │ │ │ └─ 换行符 (0x0A)
/// │ │ │ │ └─ 回车符 (0x0D)
/// │ │ │ └─ LRC 校验 (2 字符十六进制)
/// │ │ └─ 数据 (十六进制 ASCII 字符)
/// │ └─ 功能码 (2 字符十六进制)
/// └─ 起始符 (冒号 0x3A)
/// </code>
/// </para>
/// <para>
/// 编码规则 / Encoding Rules:
/// <list type="bullet">
/// <item>每个字节转换为 2 个十六进制 ASCII 字符 / Each byte converted to 2 hex ASCII characters</item>
/// <item>例如0x1A → "1A" (0x31, 0x41) / e.g., 0x1A → "1A" (0x31, 0x41)</item>
/// <item>LRC 校验:纵向冗余校验 / LRC checksum: Longitudinal Redundancy Check</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusAsciiProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]> public class ModbusAsciiProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
{ {
/// <summary> /// <summary>
/// 协议扩展,协议内容发送前调用 /// 协议扩展 (发送前调用) / Protocol Extend (Called Before Sending)
/// <remarks>
/// 将二进制数据转换为 ASCII 格式添加起始符、LRC 校验和结束符
/// Convert binary data to ASCII format, add start marker, LRC checksum and end marker
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>添加起始符 ':' (0x3A) / Add start marker ':' (0x3A)</item>
/// <item>将每个字节转换为 2 个十六进制 ASCII 字符 / Convert each byte to 2 hex ASCII characters</item>
/// <item>计算 LRC 校验码并转换为 ASCII / Calculate LRC checksum and convert to ASCII</item>
/// <item>添加结束符 CR LF (0x0D 0x0A) / Add end marker CR LF (0x0D 0x0A)</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// 原始数据 / Raw data: [0x01, 0x03, 0x00, 0x00]
/// 扩展后 / Extended: [0x3A, 0x30, 0x31, 0x30, 0x33, 0x30, 0x30, 0x30, 0x30, LRC, 0x0D, 0x0A]
/// ASCII: ":01030000[LRC][CR][LF]"
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">扩展前的原始协议内容</param> /// <param name="content">
/// <returns>扩展后的协议内容</returns> /// 扩展前的原始协议内容 / Original Protocol Content Before Extension
/// <remarks>
/// 二进制格式的 Modbus 数据
/// Binary format Modbus data
/// </remarks>
/// </param>
/// <returns>
/// 扩展后的协议内容 / Extended Protocol Content
/// <remarks>
/// ASCII 格式的完整帧
/// Complete frame in ASCII format
/// </remarks>
/// </returns>
public byte[] BytesExtend(byte[] content) public byte[] BytesExtend(byte[] content)
{ {
//Modbus/Ascii协议扩张,前面增加:后面增加LRC校验和尾字符 // Modbus/ASCII 协议扩张,前面增加:,后面增加 LRC 校验和尾字符
// Modbus/ASCII protocol extension, add ':' at front, LRC and tail characters at end
var newContent = new List<byte>(); var newContent = new List<byte>();
// 添加起始符 ':' / Add start marker ':'
newContent.AddRange(Encoding.ASCII.GetBytes(":")); newContent.AddRange(Encoding.ASCII.GetBytes(":"));
// 将每个字节转换为 2 个十六进制 ASCII 字符 / Convert each byte to 2 hex ASCII characters
foreach (var number in content) foreach (var number in content)
newContent.AddRange(Encoding.ASCII.GetBytes(number.ToString("X2"))); newContent.AddRange(Encoding.ASCII.GetBytes(number.ToString("X2")));
// 计算并添加 LRC 校验 / Calculate and add LRC checksum
newContent.AddRange(Encoding.ASCII.GetBytes(Crc16.GetInstance().GetLRC(content))); newContent.AddRange(Encoding.ASCII.GetBytes(Crc16.GetInstance().GetLRC(content)));
// 添加结束符 CR LF / Add end marker CR LF
newContent.Add(0x0d); newContent.Add(0x0d);
newContent.Add(0x0a); newContent.Add(0x0a);
return newContent.ToArray(); return newContent.ToArray();
} }
/// <summary> /// <summary>
/// 协议收缩,协议内容接收后调用 /// 协议收缩 (接收后调用) / Protocol Reduce (Called After Receiving)
/// <remarks>
/// 将 ASCII 格式数据转换回二进制格式移除起始符、LRC 校验和结束符
/// Convert ASCII format data back to binary format, remove start marker, LRC checksum and end marker
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>转换为字符串 / Convert to string</item>
/// <item>查找换行符位置 / Find newline position</item>
/// <item>移除起始符 ':' 和结束符 CRLF / Remove start marker ':' and end marker CRLF</item>
/// <item>每 2 个字符解析为一个字节 / Parse every 2 characters as one byte</item>
/// <item>移除 LRC 校验字节 / Remove LRC checksum byte</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// ASCII: ":01030000000AF8[CR][LF]"
/// 移除头尾 / Remove head and tail: "01030000000AF8"
/// 解析字节 / Parse bytes: [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xF8]
/// 移除 LRC / Remove LRC: [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">收缩前的完整协议内容</param> /// <param name="content">
/// <returns>收缩后的协议内容</returns> /// 收缩前的完整协议内容 / Complete Protocol Content Before Reduction
/// <remarks>
/// ASCII 格式的完整帧
/// Complete frame in ASCII format
/// </remarks>
/// </param>
/// <returns>
/// 收缩后的协议内容 / Reduced Protocol Content
/// <remarks>
/// 二进制格式的 Modbus 数据
/// Binary format Modbus data
/// </remarks>
/// </returns>
public byte[] BytesDecact(byte[] content) public byte[] BytesDecact(byte[] content)
{ {
//Modbus/Ascii协议收缩,抛弃头尾 // Modbus/ASCII 协议收缩,抛弃头尾
// Modbus/ASCII protocol reduction, discard head and tail
var newContent = new List<byte>(); var newContent = new List<byte>();
var ans = Encoding.ASCII.GetString(content); var ans = Encoding.ASCII.GetString(content);
// 查找换行符位置 / Find newline position
var index = ans.IndexOf(Environment.NewLine); var index = ans.IndexOf(Environment.NewLine);
// 移除起始符 ':' 和结束符 CRLF / Remove start marker ':' and end marker CRLF
ans = ans.Substring(1, index - 1); ans = ans.Substring(1, index - 1);
// 每 2 个字符解析为一个字节 / Parse every 2 characters as one byte
for (var i = 0; i < ans.Length; i += 2) for (var i = 0; i < ans.Length; i += 2)
{ {
var number = byte.Parse(ans.Substring(i, 2), NumberStyles.HexNumber); var number = byte.Parse(ans.Substring(i, 2), NumberStyles.HexNumber);
newContent.Add(number); newContent.Add(number);
} }
// 移除最后一个字节 (LRC 校验) / Remove last byte (LRC checksum)
newContent.RemoveAt(newContent.Count - 1); newContent.RemoveAt(newContent.Count - 1);
return newContent.ToArray(); return newContent.ToArray();
} }
} }
#endregion
/// <summary>
/// Modbus RTU 协议接收器字节伸缩 / Modbus RTU Protocol Receiver Bytes Extend
/// <remarks>
/// 继承自 ModbusRtuProtocolLinkerBytesExtend用于服务器端
/// Inherits from ModbusRtuProtocolLinkerBytesExtend, for server-side use
/// </remarks>
/// </summary>
public class ModbusRtuProtocolReceiverBytesExtend : ModbusRtuProtocolLinkerBytesExtend
{
// 使用 RTU 协议的字节伸缩方法 / Use RTU protocol bytes extend methods
}
} }

View File

@@ -0,0 +1,317 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
using DataReturnDef = Modbus.Net.DataReturnDef<string, double>;
namespace Modbus.Net.Modbus
{
/// <summary>
/// Modbus RTU 数据接收器类 / Modbus RTU Data Receiver Class
/// <remarks>
/// 实现 Modbus RTU 从站/服务器端的数据接收和处理功能
/// Implements data reception and processing functionality for Modbus RTU slave/server side
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>监听 Modbus RTU 请求 / Listen for Modbus RTU requests</item>
/// <item>处理读写寄存器操作 / Handle read/write register operations</item>
/// <item>处理读写线圈操作 / Handle read/write coil operations</item>
/// <item>数据格式化和缩放 / Data formatting and scaling</item>
/// <item>事件通知数据变化 / Event notification for data changes</item>
/// </list>
/// </para>
/// <para>
/// 数据存储 / Data Storage:
/// <list type="bullet">
/// <item><strong>zerox</strong> - 线圈数据数组 (10000 点) / Coil data array (10000 points)</item>
/// <item><strong>threex</strong> - 寄存器数据数组 (20000 字节) / Register data array (20000 bytes)</item>
/// </list>
/// </para>
/// <para>
/// 配置要求 / Configuration Requirements:
/// <code>
/// {
/// "Modbus.Net": {
/// "Receiver": [
/// {
/// "a:id": "EventData",
/// "e:connectionString": "COM1",
/// "h:slaveAddress": 1,
/// "f:addressMap": "AddressMapModbus",
/// "j:endian": "BigEndianLsb"
/// }
/// ]
/// }
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class ModbusRtuDataReceiver
{
/// <summary>
/// 接收器字典 / Receiver Dictionary
/// <remarks>
/// 存储 ModbusRtuProtocolReceiver 实例和最后接收时间
/// Stores ModbusRtuProtocolReceiver instances and last receive time
/// </remarks>
/// </summary>
private Dictionary<ModbusRtuProtocolReceiver, DateTime> _receivers;
/// <summary>
/// 配置根对象 / Configuration Root Object
/// <remarks>
/// 从 appsettings.json 加载配置
/// Load configuration from appsettings.json
/// </remarks>
/// </summary>
private readonly IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true)
.Build();
/// <summary>
/// 线圈数据数组 / Coil Data Array
/// <remarks>
/// 存储 10000 个线圈的状态 (0X 区)
/// Stores status of 10000 coils (0X area)
/// </remarks>
/// </summary>
private bool[] zerox = new bool[10000];
/// <summary>
/// 寄存器数据数组 / Register Data Array
/// <remarks>
/// 存储 20000 字节的寄存器数据 (4X 区10000 个寄存器)
/// Stores 20000 bytes of register data (4X area, 10000 registers)
/// </remarks>
/// </summary>
private byte[] threex = new byte[20000];
/// <summary>
/// 返回值委托 / Return Values Delegate
/// <remarks>
/// 用于通知数据变化事件
/// Used to notify data change events
/// </remarks>
/// </summary>
public delegate Dictionary<string, double> ReturnValuesDelegate(DataReturnDef returnValues);
/// <summary>
/// 返回值字典事件 / Return Value Dictionary Event
/// <remarks>
/// 当数据变化时触发,通知订阅者
/// Triggered when data changes, notifies subscribers
/// </remarks>
/// </summary>
public event ReturnValuesDelegate ReturnValueDictionary;
/// <summary>
/// 添加值到值字典 / Add Value to Value Dictionary
/// <remarks>
/// 根据数据类型将值添加到相应的字典中
/// Add values to appropriate dictionaries based on data type
/// <para>
/// 支持的数据类型 / Supported Data Types:
/// <list type="bullet">
/// <item><strong>Id</strong> - 按 ID 索引 / Index by ID</item>
/// <item><strong>Name</strong> - 按名称索引 / Index by Name</item>
/// <item><strong>Address</strong> - 按地址索引 / Index by Address</item>
/// <item><strong>CommunicationTag</strong> - 按通信标签索引 / Index by Communication Tag</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="valueDic">值字典 / Value Dictionary</param>
/// <param name="returnDic">返回字典 / Return Dictionary</param>
/// <param name="address">地址单元 / Address Unit</param>
/// <param name="value">值 / Value</param>
/// <param name="dataType">数据类型 / Data Type</param>
protected void AddValueToValueDic(Dictionary<string, double> valueDic, Dictionary<string, ReturnUnit<double>> returnDic, AddressUnit address, double value, MachineDataType dataType)
{
switch (dataType)
{
case MachineDataType.Id:
{
valueDic.Add(address.Id, value);
returnDic.Add(address.Id, new ReturnUnit<double>() { AddressUnit = address.MapAddressUnitTUnitKeyToAddressUnit(), DeviceValue = value });
break;
}
case MachineDataType.Name:
{
valueDic.Add(address.Name, value);
returnDic.Add(address.Name, new ReturnUnit<double>() { AddressUnit = address.MapAddressUnitTUnitKeyToAddressUnit(), DeviceValue = value });
break;
}
case MachineDataType.Address:
{
valueDic.Add(new AddressFormaterModbus().FormatAddress(address.Area, address.Address, address.SubAddress), value);
returnDic.Add(new AddressFormaterModbus().FormatAddress(address.Area, address.Address, address.SubAddress), new ReturnUnit<double>() { AddressUnit = address.MapAddressUnitTUnitKeyToAddressUnit(), DeviceValue = value });
break;
}
case MachineDataType.CommunicationTag:
{
valueDic.Add(address.CommunicationTag, value);
returnDic.Add(address.CommunicationTag, new ReturnUnit<double>() { AddressUnit = address.MapAddressUnitTUnitKeyToAddressUnit(), DeviceValue = value });
break;
}
}
}
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化 Modbus RTU 数据接收器,从配置加载接收器定义
/// Initialize Modbus RTU data receiver, load receiver definitions from configuration
/// </remarks>
/// </summary>
/// <param name="dataType">
/// 数据类型 / Data Type
/// <remarks>
/// 返回数据的索引方式
/// Indexing method for returned data
/// </remarks>
/// </param>
/// <param name="minimumElapse">
/// 最小时间间隔 (秒) / Minimum Time Interval (Seconds)
/// <remarks>
/// 数据上报的最小时间间隔
/// Minimum time interval for data reporting
/// </remarks>
/// </param>
public ModbusRtuDataReceiver(MachineDataType dataType, int minimumElapse = 0)
{
_receivers = new Dictionary<ModbusRtuProtocolReceiver,DateTime>();
var receiversDef = configuration.GetSection("Modbus.Net").GetSection("Receiver").GetChildren();
foreach (var receiverDef in receiversDef)
{
var machineName = receiverDef.GetValue<string>("a:id");
var _receiver = new ModbusRtuProtocolReceiver(receiverDef.GetValue<string>("e:connectionString"), receiverDef.GetValue<int>("h:slaveAddress"));
var addressMapName = receiverDef.GetValue<string>("f:addressMap");
var endian = ValueHelper.GetInstance(Endian.Parse(receiverDef.GetValue<string>("j:endian")));
// 设置数据处理回调 / Set data processing callback
_receiver.DataProcess = receiveContent =>
{
var returnTime = DateTime.Now;
byte[] returnBytes = null;
var readContent = new byte[receiveContent.Count * 2];
var values = receiveContent.WriteContent;
var valueDic = new Dictionary<string, double>();
var returnDic = new Dictionary<string, ReturnUnit<double>>();
List<AddressUnit> addressMap = AddressReader<string, int, int>.ReadAddresses(addressMapName).ToList();
if (values != null)
{
// 处理写操作 / Handle write operations
switch (receiveContent.FunctionCode)
{
case (byte)ModbusProtocolFunctionCode.WriteMultiRegister:
{
// 写多个寄存器 / Write multiple registers
Array.Copy(receiveContent.WriteContent, 0, threex, receiveContent.StartAddress * 2, receiveContent.WriteContent.Length);
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count);
break;
}
case (byte)ModbusProtocolFunctionCode.WriteSingleCoil:
{
// 写单个线圈 / Write single coil
if (receiveContent.WriteContent[0] == 255)
{
zerox[receiveContent.StartAddress] = true;
}
else
{
zerox[receiveContent.StartAddress] = false;
}
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.WriteContent);
break;
}
case (byte)ModbusProtocolFunctionCode.WriteMultiCoil:
{
// 写多个线圈 / Write multiple coils
var pos = 0;
List<bool> bitList = new List<bool>();
for (int i = 0; i < receiveContent.WriteByteCount; i++)
{
var bitArray = endian.GetBits(receiveContent.WriteContent, ref pos);
bitList.AddRange(bitArray.ToList());
}
Array.Copy(bitList.ToArray(), 0, zerox, receiveContent.StartAddress, bitList.Count);
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count);
break;
}
case (byte)ModbusProtocolFunctionCode.WriteSingleRegister:
{
// 写单个寄存器 / Write single register
Array.Copy(receiveContent.WriteContent, 0, threex, receiveContent.StartAddress * 2, receiveContent.Count * 2);
returnBytes = new WriteDataModbusProtocol().Format(receiveContent.SlaveAddress, receiveContent.FunctionCode, receiveContent.StartAddress, receiveContent.Count);
break;
}
}
// 读取并格式化数据 / Read and format data
try
{
for (int i = 0; i < addressMap.Count; i++)
{
var pos = (addressMap[i].Address - 1) * 2;
var subpos = addressMap[i].SubAddress;
string valueString = null;
// 根据区域读取数据 / Read data based on area
if (addressMap[i].Area == "4X")
{
// 保持寄存器 / Holding register
valueString = endian.GetValue(threex, ref pos, ref subpos, addressMap[i].DataType).ToString();
}
else if (addressMap[i].Area == "0X")
{
// 线圈 / Coil
valueString = zerox[addressMap[i].Address - 1].ToString();
}
// 布尔值转换 / Boolean conversion
if (valueString == "True") valueString = "1";
if (valueString == "False") valueString = "0";
// 解析和缩放 / Parse and scale
var value = double.Parse(valueString);
value = value * addressMap[i].Zoom;
value = Math.Round(value, addressMap[i].DecimalPos);
// 添加到字典 / Add to dictionary
AddValueToValueDic(valueDic, returnDic, addressMap[i], value, dataType);
}
// 触发事件 / Trigger event
if (machineName == "EventData" || (returnTime - _receivers[_receiver]).TotalSeconds + 0.5 >= minimumElapse)
{
if (ReturnValueDictionary != null)
{
var dataReturn = new DataReturnDef();
dataReturn.MachineId = machineName;
dataReturn.ReturnValues = new ReturnStruct<Dictionary<string, ReturnUnit<double>>>() { IsSuccess = true, Datas = returnDic };
// TODO: Continue implementation
}
}
}
catch (Exception e)
{
// TODO: Error handling
}
}
return returnBytes;
};
_receivers.Add(_receiver, DateTime.Now);
}
}
}
}

View File

@@ -1,42 +1,147 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Rtu协议tcp透传 /// Modbus RTU over TCP 协议类 / Modbus RTU over TCP Protocol Class
/// <remarks>
/// 实现 Modbus RTU 协议通过 TCP 透传的功能,用于串口服务器场景
/// Implements Modbus RTU protocol over TCP tunneling, used for serial device server scenarios
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 网络传输 RTU 帧 / Transmit RTU frames over TCP network</item>
/// <item>远程串口访问 / Remote serial access</item>
/// <item>多个 RTU 设备共享一个 TCP 连接 / Multiple RTU devices sharing one TCP connection</item>
/// </list>
/// </para>
/// <para>
/// 与 Modbus TCP 的区别 / Difference from Modbus TCP:
/// <list type="bullet">
/// <item><strong>RTU over TCP</strong> - RTU 帧原样传输,无 MBAP 头,保留 CRC / RTU frames as-is, no MBAP header, CRC preserved</item>
/// <item><strong>Modbus TCP</strong> - 添加 MBAP 头,移除 CRC / Adds MBAP header, removes CRC</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [从站地址 (1)][功能码 (1)][数据 (N)][CRC 低 (1)][CRC 高 (1)]
/// │ │ │ │ │
/// └─ 设备地址 └─ 操作类型 └─ 寄存器值 └─ CRC16 校验
/// (通过 TCP 原样传输,无 MBAP 头)
/// (Transmitted as-is over TCP, no MBAP header)
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 通过串口服务器连接 RTU 设备 / Connect RTU device via serial server
/// var protocol = new ModbusRtuInTcpProtocol(
/// "192.168.1.200", // 串口服务器 IP / Serial server IP
/// 8899, // 串口服务器端口 / Serial server port
/// slaveAddress: 1,
/// masterAddress: 0
/// );
///
/// // 连接设备 / Connect to device
/// await protocol.ConnectAsync();
///
/// // 读取数据 (RTU 帧通过 TCP 透传) / Read data (RTU frames tunneled over TCP)
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInTcpProtocol : ModbusProtocol public class ModbusRtuInTcpProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP) / Constructor (Read IP from Configuration)
/// <remarks>
/// 从配置文件读取 IP 地址创建 Modbus RTU over TCP 协议实例
/// Create Modbus RTU over TCP protocol instance with IP address read from configuration file
/// <para>
/// 配置项 / Configuration Item:
/// <code>TCP:Modbus:IP = "192.168.1.200"</code>
/// </para>
/// <para>
/// 默认端口 / Default Port: 由配置决定或由 ProtocolLinker 决定
/// Determined by configuration or ProtocolLinker
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// <param name="masterAddress">主站号</param> /// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
public ModbusRtuInTcpProtocol(byte slaveAddress, byte masterAddress) public ModbusRtuInTcpProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP) / Constructor (Specify IP)
/// <remarks>
/// 使用指定的 IP 地址创建 Modbus RTU over TCP 协议实例
/// Create Modbus RTU over TCP protocol instance with specified IP address
/// <para>
/// 使用默认端口 (由 ProtocolLinker 决定)
/// Uses default port (determined by ProtocolLinker)
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="slaveAddress">从站号</param> /// IP 地址 / IP Address
/// <param name="masterAddress">主站号</param> /// <remarks>
/// 串口服务器地址
/// Serial device server address
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusRtuInTcpProtocol(string ip, byte slaveAddress, byte masterAddress) public ModbusRtuInTcpProtocol(string ip, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建 Modbus RTU over TCP 协议链接器
// Create Modbus RTU over TCP protocol linker
ProtocolLinker = new ModbusRtuInTcpProtocolLinker(ip); ProtocolLinker = new ModbusRtuInTcpProtocolLinker(ip);
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus RTU over TCP 协议实例
/// Create Modbus RTU over TCP protocol instance with specified IP address and port
/// <para>
/// 适用于非标准端口的串口服务器
/// Suitable for serial servers with non-standard ports
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <param name="slaveAddress">从站号</param> /// <remarks>串口服务器地址 / Serial device server address</remarks>
/// <param name="masterAddress">主站号</param> /// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 串口服务器端口,常用 8899, 502 等
/// Serial server port, commonly 8899, 502, etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusRtuInTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) public ModbusRtuInTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建带端口的 Modbus RTU over TCP 协议链接器
// Create Modbus RTU over TCP protocol linker with port
ProtocolLinker = new ModbusRtuInTcpProtocolLinker(ip, port); ProtocolLinker = new ModbusRtuInTcpProtocolLinker(ip, port);
} }
} }

View File

@@ -1,41 +1,154 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Rtu协议连接器Tcp透传 /// Modbus RTU over TCP 协议连接器 / Modbus RTU over TCP Protocol Linker
/// <remarks>
/// 实现 Modbus RTU 协议通过 TCP 透传的连接器,继承自 TcpProtocolLinker
/// Implements Modbus RTU protocol over TCP tunneling linker, inherits from TcpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>TCP 连接管理 / TCP connection management</item>
/// <item>RTU 帧原样传输 / RTU frame transparent transmission</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 CRC16 校验 / CRC16 checksum preserved</item>
/// <item>响应校验 / Response validation</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 网络传输 RTU 帧 / Transmit RTU frames over TCP network</item>
/// <item>远程串口访问 / Remote serial access</item>
/// </list>
/// </para>
/// <para>
/// 与 Modbus TCP 的区别 / Difference from Modbus TCP:
/// <list type="bullet">
/// <item><strong>RTU over TCP</strong> - RTU 帧原样传输,无 MBAP 头,保留 CRC / RTU frames as-is, no MBAP header, CRC preserved</item>
/// <item><strong>Modbus TCP</strong> - 添加 MBAP 头,移除 CRC / Adds MBAP header, removes CRC</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [从站地址 (1)][功能码 (1)][数据 (N)][CRC 低 (1)][CRC 高 (1)]
/// │ │ │ │ │
/// └─ 设备地址 └─ 操作类型 └─ 寄存器值 └─ CRC16 校验
/// (通过 TCP 原样传输,无 MBAP 头)
/// (Transmitted as-is over TCP, no MBAP header)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInTcpProtocolLinker : TcpProtocolLinker public class ModbusRtuInTcpProtocolLinker : TcpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建 Modbus RTU over TCP 连接器
/// Create Modbus RTU over TCP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>TCP:{IP}:ModbusPort - 指定 IP 的端口 / Port for specified IP</item>
/// <item>TCP:Modbus:ModbusPort - 默认 Modbus 端口 / Default Modbus port</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// 串口服务器的 IP 地址
/// Serial device server IP address
/// </remarks>
/// </param>
public ModbusRtuInTcpProtocolLinker(string ip) public ModbusRtuInTcpProtocolLinker(string ip)
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort"))) : base(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus RTU over TCP 连接器
/// Create Modbus RTU over TCP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <remarks>串口服务器的 IP 地址 / Serial device server IP address</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 串口服务器端口,常用 8899, 502 等
/// Serial server port, commonly 8899, 502, etc.
/// </remarks>
/// </param>
public ModbusRtuInTcpProtocolLinker(string ip, int port) public ModbusRtuInTcpProtocolLinker(string ip, int port)
: base(ip, port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据 /// 校验返回数据 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus RTU over TCP 响应数据
/// Validate Modbus RTU over TCP response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (TCP 连接状态) / Call base validation (TCP connection status)</item>
/// <item>检查功能码 (字节 1) / Check function code (byte 1)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// 错误码说明 / Error Code Description:
/// <list type="bullet">
/// <item>功能码 +128: 异常响应 / Function code +128: Exception response</item>
/// <item>异常码在 content[2] / Exception code in content[2]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的数据</param> /// <param name="content">
/// <returns>数据是否正确</returns> /// 设备返回的数据 / Data Returned from Device
/// <remarks>
/// RTU 帧 (无 MBAP 头)
/// RTU frame (no MBAP header)
/// </remarks>
/// </param>
/// <returns>
/// 数据是否正确 / Whether Data is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker的CheckRight不会返回null // 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// Modbus 协议错误检测 / Modbus protocol error detection
// RTU 帧第 2 字节是功能码 / Byte 1 of RTU frame is function code
if (content[1] > 127) if (content[1] > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(content[2]); throw new ModbusProtocolErrorException(content[2]);
return true; return true;
} }
} }

View File

@@ -1,26 +1,91 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Rtu协议udp透传 /// Modbus RTU over UDP 协议类 / Modbus RTU over UDP Protocol Class
/// <remarks>
/// 实现 Modbus RTU 协议通过 UDP 透传的功能,用于无连接的网络通信
/// Implements Modbus RTU protocol over UDP tunneling, used for connectionless network communication
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>UDP 广播查询 / UDP broadcast query</item>
/// <item>通过 UDP 网络传输 RTU 帧 / Transmit RTU frames over UDP network</item>
/// <item>多个 RTU 设备共享一个 UDP 端口 / Multiple RTU devices sharing one UDP port</item>
/// <item>不要求可靠性的场景 / Scenarios not requiring reliability</item>
/// </list>
/// </para>
/// <para>
/// 与 Modbus RTU over TCP 的区别 / Difference from Modbus RTU over TCP:
/// <list type="bullet">
/// <item><strong>RTU over UDP</strong> - 无连接,不保证送达,支持广播 / Connectionless, no delivery guarantee, supports broadcast</item>
/// <item><strong>RTU over TCP</strong> - 面向连接,保证送达,不支持广播 / Connection-oriented, delivery guaranteed, no broadcast</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [从站地址 (1)][功能码 (1)][数据 (N)][CRC 低 (1)][CRC 高 (1)]
/// │ │ │ │ │
/// └─ 设备地址 └─ 操作类型 └─ 寄存器值 └─ CRC16 校验
/// (通过 UDP 原样传输,无 MBAP 头)
/// (Transmitted as-is over UDP, no MBAP header)
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 通过 UDP 连接 RTU 设备 / Connect RTU device via UDP
/// var protocol = new ModbusRtuInUdpProtocol(
/// "192.168.1.200", // 设备 IP / Device IP
/// 502, // UDP 端口 / UDP port
/// slaveAddress: 1,
/// masterAddress: 0
/// );
///
/// // 连接设备 (UDP 无需真正连接) / Connect to device (UDP doesn't need real connection)
/// await protocol.ConnectAsync();
///
/// // 读取数据 (RTU 帧通过 UDP 透传) / Read data (RTU frames tunneled over UDP)
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
///
/// // UDP 广播查询示例 / UDP broadcast query example
/// var broadcastProtocol = new ModbusRtuInUdpProtocol("255.255.255.255", 502, slaveAddress: 0, masterAddress: 0);
/// // 注意:广播地址为 0所有从站都会响应
/// // Note: Broadcast address is 0, all slaves will respond
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInUdpProtocol : ModbusProtocol public class ModbusRtuInUdpProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP) / Constructor (Read IP from Configuration)
/// <remarks>
/// 从配置文件读取 IP 地址创建 Modbus RTU over UDP 协议实例
/// Create Modbus RTU over UDP protocol instance with IP address read from configuration file
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusRtuInUdpProtocol(byte slaveAddress, byte masterAddress) public ModbusRtuInUdpProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP) / Constructor (Specify IP)
/// <remarks>
/// 使用指定的 IP 地址创建 Modbus RTU over UDP 协议实例
/// Create Modbus RTU over UDP protocol instance with specified IP address
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address (可使用广播地址 255.255.255.255)</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusRtuInUdpProtocol(string ip, byte slaveAddress, byte masterAddress) public ModbusRtuInUdpProtocol(string ip, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
@@ -28,12 +93,16 @@
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus RTU over UDP 协议实例
/// Create Modbus RTU over UDP protocol instance with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">IP 地址 / IP Address</param>
/// <param name="port">端口号</param> /// <param name="port">端口号 / Port Number</param>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号</param> /// <param name="masterAddress">主站号 / Master Address</param>
public ModbusRtuInUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) public ModbusRtuInUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {

View File

@@ -1,41 +1,156 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Rtu协议连接器Udp透传 /// Modbus RTU over UDP 协议连接器 / Modbus RTU over UDP Protocol Linker
/// <remarks>
/// 实现 Modbus RTU 协议通过 UDP 透传的连接器,继承自 UdpProtocolLinker
/// Implements Modbus RTU protocol over UDP tunneling linker, inherits from UdpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>UDP 连接管理 / UDP connection management</item>
/// <item>RTU 帧原样传输 / RTU frame transparent transmission</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 CRC16 校验 / CRC16 checksum preserved</item>
/// <item>支持广播查询 / Supports broadcast query</item>
/// <item>响应校验 / Response validation</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>UDP 广播查询 / UDP broadcast query</item>
/// <item>通过 UDP 网络传输 RTU 帧 / Transmit RTU frames over UDP network</item>
/// <item>多个 RTU 设备共享一个 UDP 端口 / Multiple RTU devices sharing one UDP port</item>
/// <item>不要求可靠性的场景 / Scenarios not requiring reliability</item>
/// </list>
/// </para>
/// <para>
/// 与 RTU over TCP 的区别 / Difference from RTU over TCP:
/// <list type="bullet">
/// <item><strong>RTU over UDP</strong> - 无连接,不保证送达,支持广播 / Connectionless, no delivery guarantee, supports broadcast</item>
/// <item><strong>RTU over TCP</strong> - 面向连接,保证送达,不支持广播 / Connection-oriented, delivery guaranteed, no broadcast</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [从站地址 (1)][功能码 (1)][数据 (N)][CRC 低 (1)][CRC 高 (1)]
/// │ │ │ │ │
/// └─ 设备地址 └─ 操作类型 └─ 寄存器值 └─ CRC16 校验
/// (通过 UDP 原样传输,无 MBAP 头)
/// (Transmitted as-is over UDP, no MBAP header)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuInUdpProtocolLinker : UdpProtocolLinker public class ModbusRtuInUdpProtocolLinker : UdpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建 Modbus RTU over UDP 连接器
/// Create Modbus RTU over UDP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>UDP:{IP}:ModbusPort - 指定 IP 的端口 / Port for specified IP</item>
/// <item>UDP:Modbus:ModbusPort - 默认 Modbus 端口 / Default Modbus port</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// 目标设备的 IP 地址 (可使用广播地址 255.255.255.255)
/// Target device IP address (can use broadcast address 255.255.255.255)
/// </remarks>
/// </param>
public ModbusRtuInUdpProtocolLinker(string ip) public ModbusRtuInUdpProtocolLinker(string ip)
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort"))) : base(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus RTU over UDP 连接器
/// Create Modbus RTU over UDP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <remarks>目标设备的 IP 地址 / Target device IP address</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// UDP 端口,默认 502
/// UDP port, default 502
/// </remarks>
/// </param>
public ModbusRtuInUdpProtocolLinker(string ip, int port) public ModbusRtuInUdpProtocolLinker(string ip, int port)
: base(ip, port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据 /// 校验返回数据 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus RTU over UDP 响应数据
/// Validate Modbus RTU over UDP response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (UDP 连接状态) / Call base validation (UDP connection status)</item>
/// <item>检查功能码 (字节 1) / Check function code (byte 1)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// 错误码说明 / Error Code Description:
/// <list type="bullet">
/// <item>功能码 +128: 异常响应 / Function code +128: Exception response</item>
/// <item>异常码在 content[2] / Exception code in content[2]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的数据</param> /// <param name="content">
/// <returns>数据是否正确</returns> /// 设备返回的数据 / Data Returned from Device
/// <remarks>
/// RTU 帧 (无 MBAP 头)
/// RTU frame (no MBAP header)
/// </remarks>
/// </param>
/// <returns>
/// 数据是否正确 / Whether Data is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker的CheckRight不会返回null // 基类校验 (UDP 连接状态) / Base validation (UDP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// Modbus 协议错误检测 / Modbus protocol error detection
// RTU 帧第 2 字节是功能码 / Byte 1 of RTU frame is function code
if (content[1] > 127) if (content[1] > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(content[2]); throw new ModbusProtocolErrorException(content[2]);
return true; return true;
} }
} }

View File

@@ -1,29 +1,99 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Rtu协议 /// Modbus/RTU 协议类 / Modbus/RTU Protocol Class
/// <remarks>
/// 实现 Modbus RTU 协议,用于串行通信
/// Implements Modbus RTU protocol for serial communication
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>二进制编码,效率高 / Binary encoding, high efficiency</item>
/// <item>CRC16 校验 / CRC16 checksum</item>
/// <item>适用于 RS-232/RS-485 串口 / Suitable for RS-232/RS-485 serial</item>
/// <item>最常用的 Modbus 模式 / Most common Modbus mode</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>
/// [从站地址 (1)][功能码 (1)][数据 (N)][CRC 低 (1)][CRC 高 (1)]
/// │ │ │ │ │
/// └─ 设备地址 └─ 操作类型 └─ 寄存器值 └─ CRC16 校验
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus RTU 协议实例 / Create Modbus RTU protocol instance
/// var protocol = new ModbusRtuProtocol("COM1", slaveAddress: 1, masterAddress: 0);
///
/// // 或者从配置读取 / Or read from configuration
/// var protocolFromConfig = new ModbusRtuProtocol(slaveAddress: 1, masterAddress: 0);
/// // 配置项COM:Modbus:COM = "COM1"
///
/// // 连接设备 / Connect to device
/// await protocol.ConnectAsync();
///
/// // 发送读取请求 / Send read request
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuProtocol : ModbusProtocol public class ModbusRtuProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取串口) / Constructor (Read COM Port from Configuration)
/// <remarks>
/// 从配置文件读取串口名称创建 Modbus RTU 协议实例
/// Create Modbus RTU protocol instance with COM port name read from configuration file
/// <para>
/// 配置项 / Configuration Item:
/// <code>COM:Modbus:COM = "COM1"</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// <param name="masterAddress">主站号</param> /// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
public ModbusRtuProtocol(byte slaveAddress, byte masterAddress) public ModbusRtuProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("COM:Modbus", "COM"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("COM:Modbus", "COM"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定串口) / Constructor (Specify COM Port)
/// <remarks>
/// 使用指定的串口名称创建 Modbus RTU 协议实例
/// Create Modbus RTU protocol instance with specified COM port name
/// <para>
/// 串口配置从 appsettings.json 读取
/// Serial port configuration is read from appsettings.json
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口名称 / COM Port Name
/// <param name="masterAddress">主站号</param> /// <remarks>如 "COM1", "COM2" 等 / e.g., "COM1", "COM2", etc.</remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusRtuProtocol(string com, byte slaveAddress, byte masterAddress) public ModbusRtuProtocol(string com, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建 Modbus RTU 协议链接器 / Create Modbus RTU protocol linker
// 自动从配置读取串口参数 (波特率、校验位等)
// Automatically read serial port parameters from configuration (baud rate, parity, etc.)
ProtocolLinker = new ModbusRtuProtocolLinker(com, slaveAddress); ProtocolLinker = new ModbusRtuProtocolLinker(com, slaveAddress);
} }
} }

View File

@@ -1,35 +1,135 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Rtu协议连接器 /// Modbus/RTU 协议连接器 / Modbus/RTU Protocol Linker
/// <remarks>
/// 实现 Modbus RTU 协议的连接器,继承自 ComProtocolLinker
/// Implements Modbus RTU protocol linker, inherits from ComProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>串口连接管理 / Serial connection management</item>
/// <item>CRC16 校验 / CRC16 checksum</item>
/// <item>响应校验 / Response validation</item>
/// <item>错误检测 / Error detection</item>
/// </list>
/// </para>
/// <para>
/// RTU 帧格式 / RTU Frame Format:
/// <code>
/// [从站地址 (1)][功能码 (1)][数据 (N)][CRC 低 (1)][CRC 高 (1)]
/// │ │ │ │ │
/// └─ 设备地址 └─ 操作类型 └─ 寄存器值 └─ CRC16 校验
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus RTU 连接器 / Create Modbus RTU linker
/// var linker = new ModbusRtuProtocolLinker("COM1", slaveAddress: 1);
///
/// // 连接设备 / Connect to device
/// await linker.ConnectAsync();
///
/// // 发送数据 / Send data
/// byte[] request = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B];
/// byte[] response = await linker.SendReceiveAsync(request);
///
/// // CheckRight 会自动校验 CRC 和协议错误
/// // CheckRight will automatically validate CRC and protocol errors
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusRtuProtocolLinker : ComProtocolLinker public class ModbusRtuProtocolLinker : ComProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 Modbus RTU 协议连接器
/// Initialize Modbus RTU protocol linker
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口地址</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口地址 / Serial Port Address
/// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// Modbus 从站地址,范围 1-247
/// Modbus slave address, range 1-247
/// </remarks>
/// </param>
public ModbusRtuProtocolLinker(string com, int slaveAddress) public ModbusRtuProtocolLinker(string com, int slaveAddress)
: base(com, slaveAddress) : base(com, slaveAddress)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据 /// 校验返回数据 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus RTU 响应数据
/// Validate Modbus RTU response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (串口连接状态) / Call base validation (serial connection status)</item>
/// <item>CRC16 校验 / CRC16 checksum</item>
/// <item>如果 CRC 失败,抛出错误 / If CRC fails, throw error</item>
/// <item>检查功能码 / Check function code</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// 错误码说明 / Error Code Description:
/// <list type="bullet">
/// <item>501: CRC 校验失败 / CRC check failed</item>
/// <item>功能码 +128: 异常响应 / Function code +128: Exception response</item>
/// <item>异常码在 content[2] / Exception code in content[2]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的数据</param> /// <param name="content">
/// <returns>数据是否正确</returns> /// 设备返回的数据 / Data Returned from Device
/// <remarks>
/// 包含 CRC 的完整 Modbus RTU 响应
/// Complete Modbus RTU response with CRC
/// </remarks>
/// </param>
/// <returns>
/// 数据是否正确 / Whether Data is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当 CRC 失败或功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when CRC fails or function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker的CheckRight不会返回null // 基类校验 (串口连接状态) / Base validation (serial connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//CRC校验失败
// CRC16 校验 / CRC16 checksum
if (!Crc16.GetInstance().CrcEfficacy(content)) if (!Crc16.GetInstance().CrcEfficacy(content))
throw new ModbusProtocolErrorException(501); throw new ModbusProtocolErrorException(501); // CRC 校验失败 / CRC check failed
//Modbus协议错误
// Modbus 协议错误检测 / Modbus protocol error detection
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
if (content[1] > 127) if (content[1] > 127)
throw new ModbusProtocolErrorException(content[2]); throw new ModbusProtocolErrorException(content[2]); // 异常码 / Exception code
return true; return true;
} }
} }

View File

@@ -0,0 +1,164 @@
using System;
namespace Modbus.Net.Modbus
{
/// <summary>
/// Modbus RTU 协议接收器类 / Modbus RTU Protocol Receiver Class
/// <remarks>
/// 实现 Modbus RTU 从站/服务器端的数据接收和解析功能
/// Implements data reception and parsing functionality for Modbus RTU slave/server side
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>接收 Modbus RTU 请求帧 / Receive Modbus RTU request frames</item>
/// <item>解析请求数据 / Parse request data</item>
/// <item>提取功能码、地址、数据等信息 / Extract function code, address, data, etc.</item>
/// <item>支持读写操作解析 / Supports read/write operation parsing</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <list type="bullet">
/// <item><strong>读操作</strong> (6 字节): [从站][功能码][地址 (2)][数量 (2)][CRC]</item>
/// <item><strong>写单线圈/寄存器</strong> (6 字节): [从站][功能码][地址 (2)][值 (2)][CRC]</item>
/// <item><strong>写多线圈/寄存器</strong> (N+7 字节): [从站][功能码][地址 (2)][数量 (2)][字节数][数据...][CRC]</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 RTU 协议接收器 / Create RTU protocol receiver
/// var receiver = new ModbusRtuProtocolReceiver("COM1", slaveAddress: 1);
///
/// // 接收器会自动解析接收到的 RTU 帧
/// // Receiver will automatically parse received RTU frames
///
/// // 解析结果示例 / Parse result example:
/// // 接收:[0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B]
/// // 解析:
/// // - SlaveAddress: 0x01
/// // - FunctionCode: 0x03 (读保持寄存器)
/// // - StartAddress: 0x0000
/// // - Count: 0x000A (10 个寄存器)
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class ModbusRtuProtocolReceiver : ProtocolReceiver
{
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化 Modbus RTU 协议接收器
/// Initialize Modbus RTU protocol receiver
/// </remarks>
/// </summary>
/// <param name="com">
/// 串口名称 / Serial Port Name
/// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// Modbus 从站地址,范围 1-247
/// Modbus slave address, range 1-247
/// </remarks>
/// </param>
public ModbusRtuProtocolReceiver(string com, int slaveAddress)
: base(com, slaveAddress)
{
}
/// <summary>
/// 数据解析函数 / Data Explanation Function
/// <remarks>
/// 将接收到的字节数组解析为 ReceiveDataDef 结构
/// Parse received byte array into ReceiveDataDef structure
/// <para>
/// 解析规则 / Parsing Rules:
/// <list type="bullet">
/// <item><strong>长度&gt;6</strong>: 写多线圈/寄存器操作 / Write multiple coils/registers</item>
/// <item><strong>长度=6</strong>: 读操作或写单线圈/寄存器 / Read operation or write single coil/register</item>
/// <item><strong>长度&lt;6</strong>: 无效帧 / Invalid frame</item>
/// </list>
/// </para>
/// <para>
/// 返回数据结构 / Return Data Structure:
/// <list type="bullet">
/// <item><strong>SlaveAddress</strong> - 从站地址 / Slave address</item>
/// <item><strong>FunctionCode</strong> - 功能码 / Function code</item>
/// <item><strong>StartAddress</strong> - 起始地址 / Start address</item>
/// <item><strong>Count</strong> - 数量 / Count</item>
/// <item><strong>WriteByteCount</strong> - 写字节数 / Write byte count</item>
/// <item><strong>WriteContent</strong> - 写入的数据 / Write data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
protected override Func<byte[], ReceiveDataDef> DataExplain
{
get
{
return receiveBytes =>
{
// 写多线圈/寄存器操作 (长度&gt;6)
// Write multiple coils/registers operation (length&gt;6)
var writeContent = receiveBytes.Length > 6 ? new byte[receiveBytes.Length - 7] : null;
if (receiveBytes.Length > 6)
{
Array.Copy(receiveBytes, 7, writeContent, 0, receiveBytes.Length - 7);
return new ReceiveDataDef()
{
SlaveAddress = receiveBytes[0],
FunctionCode = receiveBytes[1],
StartAddress = (ushort)(receiveBytes[2] * 256 + receiveBytes[3]),
Count = (ushort)(receiveBytes[4] * 256 + receiveBytes[5]),
WriteByteCount = (byte)(receiveBytes.Length > 6 ? receiveBytes[6] : 0),
WriteContent = writeContent
};
}
// 读操作或写单线圈/寄存器 (长度=6)
// Read operation or write single coil/register (length=6)
else if (receiveBytes.Length == 6)
{
// 读线圈 (01) 或读寄存器 (03)
// Read coils (01) or read registers (03)
if (receiveBytes[1] == 1 || receiveBytes[1] == 3)
{
writeContent = null;
return new ReceiveDataDef()
{
SlaveAddress = receiveBytes[0],
FunctionCode = receiveBytes[1],
StartAddress = (ushort)(receiveBytes[2] * 256 + receiveBytes[3]),
Count = (ushort)(receiveBytes[4] * 256 + receiveBytes[5]),
WriteByteCount = 2,
WriteContent = writeContent
};
}
// 写单线圈 (05) 或写单寄存器 (06)
// Write single coil (05) or write single register (06)
else
{
writeContent = new byte[2] { receiveBytes[4], receiveBytes[5] };
return new ReceiveDataDef()
{
SlaveAddress = receiveBytes[0],
FunctionCode = receiveBytes[1],
StartAddress = (ushort)(receiveBytes[2] * 256 + receiveBytes[3]),
Count = 1,
WriteByteCount = 2,
WriteContent = writeContent
};
}
}
// 无效帧 / Invalid frame
else return null;
};
}
}
}
}

View File

@@ -1,42 +1,131 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Tcp协议 /// Modbus/TCP 协议类 / Modbus/TCP Protocol Class
/// <remarks>
/// 实现 Modbus TCP 协议,用于以太网通信
/// Implements Modbus TCP protocol for Ethernet communication
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 TCP/IP 传输 / Based on TCP/IP transport</item>
/// <item>添加 MBAP 头 (6 字节) / Adds MBAP header (6 bytes)</item>
/// <item>无需 CRC 校验 (TCP 已保证) / No CRC needed (TCP guarantees)</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// <para>
/// MBAP 头格式 / MBAP Header Format:
/// <code>
/// [Transaction ID (2 字节)][Protocol ID (2 字节)][Length (2 字节)][Unit ID (1 字节)]
/// │ │ │ │ │ │ │
/// └─ 事务标识,用于匹配请求响应
/// └─ 协议标识Modbus=0
/// └─ 后续字节长度
/// └─ 从站地址
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus TCP 协议实例 / Create Modbus TCP protocol instance
/// var protocol = new ModbusTcpProtocol("192.168.1.100", 502, slaveAddress: 1, masterAddress: 0);
///
/// // 或者从配置读取 / Or read from configuration
/// var protocolFromConfig = new ModbusTcpProtocol(slaveAddress: 1, masterAddress: 0);
/// // 配置项TCP:Modbus:IP = "192.168.1.100"
///
/// // 连接设备 / Connect to device
/// await protocol.ConnectAsync();
///
/// // 发送读取请求 / Send read request
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusTcpProtocol : ModbusProtocol public class ModbusTcpProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP) / Constructor (Read IP from Configuration)
/// <remarks>
/// 从配置文件读取 IP 地址创建 Modbus TCP 协议实例
/// Create Modbus TCP protocol instance with IP address read from configuration file
/// <para>
/// 配置项 / Configuration Item:
/// <code>TCP:Modbus:IP = "192.168.1.100"</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// <param name="masterAddress">主站号</param> /// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
public ModbusTcpProtocol(byte slaveAddress, byte masterAddress) public ModbusTcpProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP) / Constructor (Specify IP)
/// <remarks>
/// 使用指定的 IP 地址创建 Modbus TCP 协议实例
/// Create Modbus TCP protocol instance with specified IP address
/// <para>
/// 使用默认端口 502
/// Uses default port 502
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="slaveAddress">从站号</param> /// IP 地址 / IP Address
/// <param name="masterAddress">主站号</param> /// <remarks>如 "192.168.1.100" / e.g., "192.168.1.100"</remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusTcpProtocol(string ip, byte slaveAddress, byte masterAddress) public ModbusTcpProtocol(string ip, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建 Modbus TCP 协议链接器 / Create Modbus TCP protocol linker
ProtocolLinker = new ModbusTcpProtocolLinker(ip); ProtocolLinker = new ModbusTcpProtocolLinker(ip);
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus TCP 协议实例
/// Create Modbus TCP protocol instance with specified IP address and port
/// <para>
/// 适用于非标准端口的 Modbus TCP 设备
/// Suitable for Modbus TCP devices with non-standard ports
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口</param> /// IP 地址 / IP Address
/// <param name="slaveAddress">从站号</param> /// <remarks>如 "192.168.1.100" / e.g., "192.168.1.100"</remarks>
/// <param name="masterAddress">主站号</param> /// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 默认 502串口服务器常用 8899
/// Default 502, commonly 8899 for serial servers
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) public ModbusTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建带端口的 Modbus TCP 协议链接器 / Create Modbus TCP protocol linker with port
ProtocolLinker = new ModbusTcpProtocolLinker(ip, port); ProtocolLinker = new ModbusTcpProtocolLinker(ip, port);
} }
} }

View File

@@ -1,40 +1,155 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Tcp协议连接器 /// Modbus/TCP 协议连接器 / Modbus/TCP Protocol Linker
/// <remarks>
/// 实现 Modbus TCP 协议的连接器,继承自 TcpProtocolLinker
/// Implements Modbus TCP protocol linker, inherits from TcpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>TCP 连接管理 / TCP connection management</item>
/// <item>MBAP 头处理 / MBAP header handling</item>
/// <item>响应校验 / Response validation</item>
/// <item>错误检测 / Error detection</item>
/// </list>
/// </para>
/// <para>
/// MBAP 头格式 / MBAP Header Format:
/// <code>
/// [Transaction ID (2)][Protocol ID (2)][Length (2)][Unit ID (1)]
/// │ │ │ │ │ │
/// └─ 事务标识,用于匹配请求响应
/// └─ 协议标识Modbus=0
/// └─ 后续字节长度
/// └─ 从站地址
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus TCP 连接器 / Create Modbus TCP linker
/// var linker = new ModbusTcpProtocolLinker("192.168.1.100", 502);
///
/// // 连接设备 / Connect to device
/// await linker.ConnectAsync();
///
/// // 发送数据 / Send data
/// byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
/// byte[] response = await linker.SendReceiveAsync(request);
///
/// // CheckRight 会自动校验响应
/// // CheckRight will automatically validate response
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusTcpProtocolLinker : TcpProtocolLinker public class ModbusTcpProtocolLinker : TcpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建 Modbus TCP 连接器
/// Create Modbus TCP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>TCP:{IP}:ModbusPort - 指定 IP 的端口 / Port for specified IP</item>
/// <item>TCP:Modbus:ModbusPort - 默认 Modbus 端口 / Default Modbus port</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// Modbus TCP 设备的 IP 地址
/// IP address of Modbus TCP device
/// </remarks>
/// </param>
public ModbusTcpProtocolLinker(string ip) public ModbusTcpProtocolLinker(string ip)
: this(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort"))) : this(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus TCP 连接器
/// Create Modbus TCP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// <param name="port">端口</param> /// IP 地址 / IP Address
/// <remarks>Modbus TCP 设备的 IP 地址 / IP address of Modbus TCP device</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// Modbus TCP 默认端口502
/// Modbus TCP default port: 502
/// </remarks>
/// </param>
public ModbusTcpProtocolLinker(string ip, int port) : base(ip, port) public ModbusTcpProtocolLinker(string ip, int port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据 /// 校验返回数据 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus TCP 响应数据
/// Validate Modbus TCP response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (TCP 连接状态) / Call base validation (TCP connection status)</item>
/// <item>检查 MBAP 头第 8 字节 (功能码) / Check MBAP header byte 8 (function code)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// 错误码说明 / Error Code Description:
/// <list type="bullet">
/// <item>功能码 +128: 异常响应 / Function code +128: Exception response</item>
/// <item>异常码在 content[8] / Exception code in content[8]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的数据</param> /// <param name="content">
/// <returns>数据是否正确</returns> /// 设备返回的数据 / Data Returned from Device
/// <remarks>
/// 包含 MBAP 头的完整 Modbus TCP 响应
/// Complete Modbus TCP response with MBAP header
/// </remarks>
/// </param>
/// <returns>
/// 数据是否正确 / Whether Data is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker的CheckRight不会返回null // 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// Modbus 协议错误检测 / Modbus protocol error detection
// MBAP 头第 8 字节是功能码 / Byte 8 of MBAP header is function code
if (content[7] > 127) if (content[7] > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]); throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]);
return true; return true;
} }
} }

View File

@@ -0,0 +1,222 @@
using System;
namespace Modbus.Net.Modbus
{
/// <summary>
/// Modbus 连接类型枚举 / Modbus Connection Type Enum
/// <remarks>
/// 定义 Modbus 协议支持的各种连接方式
/// Defines various connection methods supported by Modbus protocol
/// <para>
/// 连接类型说明 / Connection Type Description:
/// <list type="bullet">
/// <item><strong>Rtu</strong> - 串行 RTU 模式 (最常用) / Serial RTU mode (most common)</item>
/// <item><strong>Tcp</strong> - 以太网 TCP 模式 / Ethernet TCP mode</item>
/// <item><strong>Ascii</strong> - 串行 ASCII 模式 / Serial ASCII mode</item>
/// <item><strong>RtuInTcp</strong> - TCP 透传 RTU 数据 / RTU data over TCP tunneling</item>
/// <item><strong>AsciiInTcp</strong> - TCP 透传 ASCII 数据 / ASCII data over TCP tunneling</item>
/// <item><strong>Udp</strong> - UDP 模式 / UDP mode</item>
/// <item><strong>RtuInUdp</strong> - UDP 透传 RTU 数据 / RTU data over UDP tunneling</item>
/// <item><strong>AsciiInUdp</strong> - UDP 透传 ASCII 数据 / ASCII data over UDP tunneling</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // Modbus TCP 连接 / Modbus TCP connection
/// var utility = new ModbusUtility(
/// ModbusType.Tcp,
/// "192.168.1.100:502",
/// slaveAddress: 1,
/// masterAddress: 0,
/// endian: Endian.BigEndianLsb
/// );
///
/// // Modbus RTU 串口连接 / Modbus RTU serial connection
/// var serialUtility = new ModbusUtility(
/// ModbusType.Rtu,
/// "COM1",
/// slaveAddress: 1,
/// masterAddress: 0,
/// endian: Endian.BigEndianLsb
/// );
///
/// // Modbus RTU over TCP (串口服务器) / Modbus RTU over TCP (Serial Server)
/// var tunnelUtility = new ModbusUtility(
/// ModbusType.RtuInTcp,
/// "192.168.1.200:8899", // 串口服务器地址
/// slaveAddress: 1,
/// masterAddress: 0,
/// endian: Endian.BigEndianLsb
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary>
public enum ModbusType
{
/// <summary>
/// RTU 连接 (串行) / RTU Connection (Serial)
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>二进制编码,效率高 / Binary encoding, high efficiency</item>
/// <item>CRC16 校验 / CRC16 checksum</item>
/// <item>最常用的 Modbus 模式 / Most common Modbus mode</item>
/// <item>适用于 RS-232/RS-485 串口 / Suitable for RS-232/RS-485 serial</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>[从站地址][功能码][数据][CRC 低][CRC 高]</code>
/// </para>
/// </remarks>
/// </summary>
Rtu = 0,
/// <summary>
/// TCP 连接 (以太网) / TCP Connection (Ethernet)
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>基于以太网 / Based on Ethernet</item>
/// <item>添加 MBAP 头 (6 字节) / Adds MBAP header (6 bytes)</item>
/// <item>无需 CRC 校验 (TCP 已保证) / No CRC needed (TCP guarantees)</item>
/// <item>端口502 / Port: 502</item>
/// </list>
/// </para>
/// <para>
/// MBAP 头格式 / MBAP Header Format:
/// <code>[Transaction ID (2)][Protocol ID (2)][Length (2)][Unit ID (1)]</code>
/// </para>
/// </remarks>
/// </summary>
Tcp = 1,
/// <summary>
/// ASCII 连接 (串行) / ASCII Connection (Serial)
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>ASCII 字符编码 / ASCII character encoding</item>
/// <item>LRC 校验 / LRC checksum</item>
/// <item>以冒号 (:) 开始CRLF 结束 / Starts with colon (:), ends with CRLF</item>
/// <item>效率较低,但易于调试 / Lower efficiency, but easy to debug</item>
/// </list>
/// </para>
/// <para>
/// 帧格式 / Frame Format:
/// <code>: [从站地址][功能码][数据][LRC][CR][LF]</code>
/// </para>
/// </remarks>
/// </summary>
Ascii = 2,
/// <summary>
/// RTU 连接 TCP 透传 / RTU Connection TCP Tunneling
/// <remarks>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 传输 RTU 帧 / Transmit RTU frames over TCP</item>
/// <item>远程串口访问 / Remote serial access</item>
/// </list>
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>RTU 帧原样传输 / RTU frames transmitted as-is</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 CRC 校验 / CRC checksum preserved</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
RtuInTcp = 3,
/// <summary>
/// ASCII 连接 TCP 透传 / ASCII Connection TCP Tunneling
/// <remarks>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口服务器 / Serial device server</item>
/// <item>通过 TCP 传输 ASCII 帧 / Transmit ASCII frames over TCP</item>
/// </list>
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>ASCII 帧原样传输 / ASCII frames transmitted as-is</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 LRC 校验 / LRC checksum preserved</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
AsciiInTcp = 4,
/// <summary>
/// UDP 连接 / UDP Connection
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>无连接模式 / Connectionless mode</item>
/// <item>添加 MBAP 头 / Adds MBAP header</item>
/// <item>不保证可靠性 / No reliability guarantee</item>
/// <item>适用于广播 / Suitable for broadcast</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
Udp = 5,
/// <summary>
/// RTU 连接 UDP 透传 / RTU Connection UDP Tunneling
/// <remarks>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>UDP 广播查询 / UDP broadcast query</item>
/// <item>通过 UDP 传输 RTU 帧 / Transmit RTU frames over UDP</item>
/// </list>
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>RTU 帧原样传输 / RTU frames transmitted as-is</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 CRC 校验 / CRC checksum preserved</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
RtuInUdp = 6,
/// <summary>
/// ASCII 连接 UDP 透传 / ASCII Connection UDP Tunneling
/// <remarks>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>通过 UDP 传输 ASCII 帧 / Transmit ASCII frames over UDP</item>
/// </list>
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>ASCII 帧原样传输 / ASCII frames transmitted as-is</item>
/// <item>无 MBAP 头 / No MBAP header</item>
/// <item>保留 LRC 校验 / LRC checksum preserved</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
AsciiInUdp = 7
}
}

View File

@@ -1,42 +1,153 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Udp协议 /// Modbus/UDP 协议类 / Modbus/UDP Protocol Class
/// <remarks>
/// 实现 Modbus UDP 协议,用于无连接的以太网通信
/// Implements Modbus UDP protocol for connectionless Ethernet communication
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 UDP/IP 传输 / Based on UDP/IP transport</item>
/// <item>添加 MBAP 头 (6 字节) / Adds MBAP header (6 bytes)</item>
/// <item>无连接模式 / Connectionless mode</item>
/// <item>不保证可靠性 / No reliability guarantee</item>
/// <item>适用于广播查询 / Suitable for broadcast query</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// <para>
/// 与 Modbus TCP 的区别 / Difference from Modbus TCP:
/// <list type="bullet">
/// <item><strong>UDP</strong> - 无连接,不保证送达,支持广播 / Connectionless, no delivery guarantee, supports broadcast</item>
/// <item><strong>TCP</strong> - 面向连接,保证送达,不支持广播 / Connection-oriented, delivery guaranteed, no broadcast</item>
/// </list>
/// </para>
/// <para>
/// MBAP 头格式 / MBAP Header Format:
/// <code>
/// [Transaction ID (2)][Protocol ID (2)][Length (2)][Unit ID (1)]
/// │ │ │ │ │ │
/// └─ 事务标识,用于匹配请求响应
/// └─ 协议标识Modbus=0
/// └─ 后续字节长度
/// └─ 从站地址
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus UDP 协议实例 / Create Modbus UDP protocol instance
/// var protocol = new ModbusUdpProtocol("192.168.1.100", 502, slaveAddress: 1, masterAddress: 0);
///
/// // 或者从配置读取 / Or read from configuration
/// var protocolFromConfig = new ModbusUdpProtocol(slaveAddress: 1, masterAddress: 0);
/// // 配置项UDP:Modbus:IP = "192.168.1.100"
///
/// // 连接设备 (UDP 无需真正连接) / Connect to device (UDP doesn't need real connection)
/// await protocol.ConnectAsync();
///
/// // 发送读取请求 / Send read request
/// var inputStruct = new ReadDataModbusInputStruct(1, "4X 1", 10, addressTranslator, 0);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataModbusOutputStruct&gt;(
/// protocol[typeof(ReadDataModbusProtocol)],
/// inputStruct
/// );
///
/// // UDP 广播查询示例 / UDP broadcast query example
/// var broadcastProtocol = new ModbusUdpProtocol("255.255.255.255", 502, slaveAddress: 0, masterAddress: 0);
/// // 注意:广播地址为 0所有从站都会响应
/// // Note: Broadcast address is 0, all slaves will respond
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusUdpProtocol : ModbusProtocol public class ModbusUdpProtocol : ModbusProtocol
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP) / Constructor (Read IP from Configuration)
/// <remarks>
/// 从配置文件读取 IP 地址创建 Modbus UDP 协议实例
/// Create Modbus UDP protocol instance with IP address read from configuration file
/// <para>
/// 配置项 / Configuration Item:
/// <code>UDP:Modbus:IP = "192.168.1.100"</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// <param name="masterAddress">主站号</param> /// 从站号 / Slave Address
/// <remarks>Modbus 从站地址,范围 1-247 / Modbus slave address, range 1-247</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>通常为 0 或 1 / Usually 0 or 1</remarks>
/// </param>
public ModbusUdpProtocol(byte slaveAddress, byte masterAddress) public ModbusUdpProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP) / Constructor (Specify IP)
/// <remarks>
/// 使用指定的 IP 地址创建 Modbus UDP 协议实例
/// Create Modbus UDP protocol instance with specified IP address
/// <para>
/// 使用默认端口 502
/// Uses default port 502
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="slaveAddress">从站号</param> /// IP 地址 / IP Address
/// <param name="masterAddress">主站号</param> /// <remarks>
/// 如 "192.168.1.100" 或广播地址 "255.255.255.255"
/// e.g., "192.168.1.100" or broadcast address "255.255.255.255"
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusUdpProtocol(string ip, byte slaveAddress, byte masterAddress) public ModbusUdpProtocol(string ip, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建 Modbus UDP 协议链接器
// Create Modbus UDP protocol linker
ProtocolLinker = new ModbusUdpProtocolLinker(ip); ProtocolLinker = new ModbusUdpProtocolLinker(ip);
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus UDP 协议实例
/// Create Modbus UDP protocol instance with specified IP address and port
/// <para>
/// 适用于非标准端口的设备
/// Suitable for devices with non-standard ports
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口</param> /// IP 地址 / IP Address
/// <param name="slaveAddress">从站号</param> /// <remarks>
/// <param name="masterAddress">主站号</param> /// 如 "192.168.1.100" 或广播地址 "255.255.255.255"
/// e.g., "192.168.1.100" or broadcast address "255.255.255.255"
/// </remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 默认 502
/// Default 502
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public ModbusUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress) public ModbusUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
// 创建带端口的 Modbus UDP 协议链接器
// Create Modbus UDP protocol linker with port
ProtocolLinker = new ModbusUdpProtocolLinker(ip, port); ProtocolLinker = new ModbusUdpProtocolLinker(ip, port);
} }
} }

View File

@@ -1,40 +1,154 @@
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus/Udp协议连接器 /// Modbus/UDP 协议连接器 / Modbus/UDP Protocol Linker
/// <remarks>
/// 实现 Modbus UDP 协议的连接器,继承自 UdpProtocolLinker
/// Implements Modbus UDP protocol linker, inherits from UdpProtocolLinker
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>UDP 连接管理 / UDP connection management</item>
/// <item>MBAP 头处理 / MBAP header handling</item>
/// <item>响应校验 / Response validation</item>
/// <item>错误检测 / Error detection</item>
/// <item>支持广播查询 / Supports broadcast query</item>
/// </list>
/// </para>
/// <para>
/// 与 TCP 的区别 / Difference from TCP:
/// <list type="bullet">
/// <item>无连接模式 / Connectionless mode</item>
/// <item>不保证可靠性 / No reliability guarantee</item>
/// <item>支持广播 / Supports broadcast</item>
/// <item>适用于简单查询场景 / Suitable for simple query scenarios</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 Modbus UDP 连接器 / Create Modbus UDP linker
/// var linker = new ModbusUdpProtocolLinker("192.168.1.100", 502);
///
/// // 连接设备 (UDP 无需真正连接) / Connect to device (UDP doesn't need real connection)
/// await linker.ConnectAsync();
///
/// // 发送数据 / Send data
/// byte[] request = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x0A];
/// byte[] response = await linker.SendReceiveAsync(request);
///
/// // CheckRight 会自动校验响应
/// // CheckRight will automatically validate response
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusUdpProtocolLinker : UdpProtocolLinker public class ModbusUdpProtocolLinker : UdpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建 Modbus UDP 连接器
/// Create Modbus UDP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>UDP:{IP}:ModbusPort - 指定 IP 的端口 / Port for specified IP</item>
/// <item>UDP:Modbus:ModbusPort - 默认 Modbus 端口 / Default Modbus port</item>
/// <item>默认端口502 / Default port: 502</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// Modbus UDP 设备的 IP 地址
/// IP address of Modbus UDP device
/// </remarks>
/// </param>
public ModbusUdpProtocolLinker(string ip) public ModbusUdpProtocolLinker(string ip)
: this(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort"))) : this(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建 Modbus UDP 连接器
/// Create Modbus UDP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// <param name="port">端口</param> /// IP 地址 / IP Address
/// <remarks>Modbus UDP 设备的 IP 地址 / IP address of Modbus UDP device</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// Modbus UDP 默认端口502
/// Modbus UDP default port: 502
/// </remarks>
/// </param>
public ModbusUdpProtocolLinker(string ip, int port) : base(ip, port) public ModbusUdpProtocolLinker(string ip, int port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验返回数据 /// 校验返回数据 / Validate Return Data
/// <remarks>
/// 校验从设备返回的 Modbus UDP 响应数据
/// Validate Modbus UDP response data returned from device
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (UDP 连接状态) / Call base validation (UDP connection status)</item>
/// <item>检查 MBAP 头第 8 字节 (功能码) / Check MBAP header byte 8 (function code)</item>
/// <item>如果功能码&gt;127表示错误响应 / If function code&gt;127, indicates error response</item>
/// <item>抛出 ModbusProtocolErrorException / Throw ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// 错误码说明 / Error Code Description:
/// <list type="bullet">
/// <item>功能码 +128: 异常响应 / Function code +128: Exception response</item>
/// <item>异常码在 content[8] / Exception code in content[8]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的数据</param> /// <param name="content">
/// <returns>数据是否正确</returns> /// 设备返回的数据 / Data Returned from Device
/// <remarks>
/// 包含 MBAP 头的完整 Modbus UDP 响应
/// Complete Modbus UDP response with MBAP header
/// </remarks>
/// </param>
/// <returns>
/// 数据是否正确 / Whether Data is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据正确 / Data correct</item>
/// <item>false: 数据错误 / Data error</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="ModbusProtocolErrorException">
/// 当功能码&gt;127 时抛出 Modbus 协议错误
/// Throw Modbus protocol error when function code&gt;127
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
//ProtocolLinker的CheckRight不会返回null // 基类校验 (UDP 连接状态) / Base validation (UDP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return base.CheckRight(content);
//Modbus协议错误
// Modbus 协议错误检测 / Modbus protocol error detection
// MBAP 头第 8 字节是功能码 / Byte 8 of MBAP header is function code
if (content[7] > 127) if (content[7] > 127)
// 功能码&gt;127 表示异常响应 / Function code&gt;127 indicates exception response
throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]); throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]);
return true; return true;
} }
} }

View File

@@ -1,57 +1,73 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Modbus namespace Modbus.Net.Modbus
{ {
/// <summary> /// <summary>
/// Modbus连接类型 /// Modbus 基础 API 入口类 / Modbus Base API Entry Class
/// </summary> /// <remarks>
public enum ModbusType /// 提供 Modbus 协议的完整实现,支持多种连接方式和高级功能
{ /// Provides complete Modbus protocol implementation, supporting multiple connection methods and advanced features
/// <summary> /// <para>
/// Rtu连接 /// 支持的连接类型 / Supported Connection Types:
/// </summary> /// <list type="bullet">
Rtu = 0, /// <item><strong>ModbusType.Rtu</strong> - 串行 RTU 模式 (最常用) / Serial RTU mode (most common)</item>
/// <item><strong>ModbusType.Tcp</strong> - 以太网 TCP 模式 / Ethernet TCP mode</item>
/// <summary> /// <item><strong>ModbusType.Ascii</strong> - 串行 ASCII 模式 / Serial ASCII mode</item>
/// Tcp连接 /// <item><strong>ModbusType.RtuInTcp</strong> - TCP 透传 RTU 数据 / RTU over TCP tunneling</item>
/// </summary> /// <item><strong>ModbusType.AsciiInTcp</strong> - TCP 透传 ASCII 数据 / ASCII over TCP tunneling</item>
Tcp = 1, /// <item><strong>ModbusType.Udp</strong> - UDP 模式 / UDP mode</item>
/// <item><strong>ModbusType.RtuInUdp</strong> - UDP 透传 RTU 数据 / RTU over UDP tunneling</item>
/// <summary> /// <item><strong>ModbusType.AsciiInUdp</strong> - UDP 透传 ASCII 数据 / ASCII over UDP tunneling</item>
/// Ascii连接 /// </list>
/// </summary> /// </para>
Ascii = 2, /// <para>
/// 实现的功能码 / Implemented Function Codes:
/// <summary> /// <list type="bullet">
/// Rtu连接Tcp透传 /// <item><strong>01</strong> - 读线圈状态 / Read Coil Status</item>
/// </summary> /// <item><strong>02</strong> - 读离散输入 / Read Discrete Inputs</item>
RtuInTcp = 3, /// <item><strong>03</strong> - 读保持寄存器 / Read Holding Registers</item>
/// <item><strong>04</strong> - 读输入寄存器 / Read Input Registers</item>
/// <summary> /// <item><strong>05</strong> - 写单个线圈 / Write Single Coil</item>
/// Ascii连接Tcp透传 /// <item><strong>06</strong> - 写单个寄存器 / Write Single Register</item>
/// </summary> /// <item><strong>15</strong> - 写多个线圈 / Write Multiple Coils</item>
AsciiInTcp = 4, /// <item><strong>16</strong> - 写多个寄存器 / Write Multiple Registers</item>
/// <item><strong>23</strong> - 读写多个寄存器 / Read/Write Multiple Registers</item>
/// <summary> /// <item><strong>07-08,11-12,17,20-22,24</strong> - 其他高级功能 / Other advanced features</item>
/// Udp连接 /// </list>
/// </summary> /// </para>
Udp = 5, /// <para>
/// 使用示例 / Usage Example:
/// <summary> /// <code>
/// Rtu连接Udp透传 /// // Modbus TCP 连接 / Modbus TCP connection
/// </summary> /// var utility = new ModbusUtility(
RtuInUdp = 6, /// ModbusType.Tcp,
/// "192.168.1.100:502",
/// <summary> /// slaveAddress: 1,
/// Ascii连接Udp透传 /// masterAddress: 0,
/// </summary> /// endian: Endian.BigEndianLsb
AsciiInUdp = 7 /// );
} ///
/// // 连接设备 / Connect to device
/// <summary> /// await utility.ConnectAsync();
/// Modbus基础Api入口 ///
/// // 读取保持寄存器 / Read holding registers
/// var result = await utility.GetDatasAsync&lt;ushort&gt;("4X 1", 10);
/// if (result.IsSuccess)
/// {
/// ushort[] values = result.Datas;
/// Console.WriteLine($"Temperature: {values[0] * 0.1}°C");
/// }
///
/// // 写入寄存器 / Write registers
/// await utility.SetDatasAsync("4X 1", new object[] { (ushort)250, (ushort)300 });
///
/// // 断开连接 / Disconnect
/// utility.Disconnect();
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class ModbusUtility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit>, public class ModbusUtility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit>,
IUtilityMethodExceptionStatus, IUtilityMethodExceptionStatus,
@@ -67,17 +83,46 @@ namespace Modbus.Net.Modbus
private static readonly ILogger<ModbusUtility> logger = LogProvider.CreateLogger<ModbusUtility>(); private static readonly ILogger<ModbusUtility> logger = LogProvider.CreateLogger<ModbusUtility>();
/// <summary> /// <summary>
/// Modbus协议类型 /// Modbus 协议类型 / Modbus Protocol Type
/// <remarks>
/// 当前使用的 Modbus 连接类型
/// Current Modbus connection type in use
/// </remarks>
/// </summary> /// </summary>
private ModbusType _modbusType; private ModbusType _modbusType;
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (无连接字符串) / Constructor (without Connection String)
/// <remarks>
/// 初始化 Modbus Utility 实例,稍后通过 SetConnectionType 设置连接
/// Initialize Modbus Utility instance, set connection later via SetConnectionType
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">协议类型</param> /// <param name="connectionType">
/// <param name="slaveAddress">从站号</param> /// 协议类型 / Protocol Type
/// <param name="masterAddress">主站号</param> /// <remarks>ModbusType 枚举值 / ModbusType enum value</remarks>
/// <param name="endian">端格式</param> /// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// Modbus 从站地址,范围 1-247
/// Modbus slave address, range 1-247
/// </remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>
/// 通常为 0 或 1
/// Usually 0 or 1
/// </remarks>
/// </param>
/// <param name="endian">
/// 端格式 / Endianness
/// <remarks>
/// Modbus 标准使用 BigEndianLsb
/// Modbus standard uses BigEndianLsb
/// </remarks>
/// </param>
public ModbusUtility(int connectionType, byte slaveAddress, byte masterAddress, public ModbusUtility(int connectionType, byte slaveAddress, byte masterAddress,
Endian endian) Endian endian)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
@@ -89,13 +134,29 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (带连接字符串) / Constructor (with Connection String)
/// <remarks>
/// 初始化 Modbus Utility 实例并立即设置连接
/// Initialize Modbus Utility instance and set connection immediately
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">协议类型</param> /// <param name="connectionType">
/// <param name="connectionString">连接地址</param> /// 协议类型 / Protocol Type
/// <param name="slaveAddress">从站号</param> /// <remarks>ModbusType 枚举值 / ModbusType enum value</remarks>
/// <param name="masterAddress">主站号</param> /// </param>
/// <param name="endian">端格式</param> /// <param name="connectionString">
/// 连接地址 / Connection Address
/// <remarks>
/// 格式示例 / Format Examples:
/// <list type="bullet">
/// <item>TCP: "192.168.1.100:502"</item>
/// <item>串口:"COM1" 或 "COM1,9600,None,8,1"</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
/// <param name="endian">端格式 / Endianness</param>
public ModbusUtility(ModbusType connectionType, string connectionString, byte slaveAddress, byte masterAddress, public ModbusUtility(ModbusType connectionType, string connectionString, byte slaveAddress, byte masterAddress,
Endian endian) Endian endian)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
@@ -107,12 +168,20 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 端格式 /// 端格式 / Endianness
/// <remarks>
/// Modbus 标准使用大端格式 (BigEndianLsb)
/// Modbus standard uses Big Endian format (BigEndianLsb)
/// </remarks>
/// </summary> /// </summary>
public override Endian Endian { get; } public override Endian Endian { get; }
/// <summary> /// <summary>
/// Ip地址 /// IP 地址 (从连接字符串提取) / IP Address (Extracted from Connection String)
/// <remarks>
/// 解析连接字符串中的 IP 部分
/// Parse IP part from connection string
/// </remarks>
/// </summary> /// </summary>
protected string ConnectionStringIp protected string ConnectionStringIp
{ {
@@ -124,7 +193,11 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 端口 /// 端口号 (从连接字符串提取) / Port Number (Extracted from Connection String)
/// <remarks>
/// 解析连接字符串中的端口部分
/// Parse port part from connection string
/// </remarks>
/// </summary> /// </summary>
protected int? ConnectionStringPort protected int? ConnectionStringPort
{ {
@@ -146,7 +219,11 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 协议类型 /// 协议类型 / Protocol Type
/// <remarks>
/// 设置协议类型时会自动创建相应的协议实例
/// Automatically creates corresponding protocol instance when setting protocol type
/// </remarks>
/// </summary> /// </summary>
public ModbusType ModbusType public ModbusType ModbusType
{ {
@@ -154,9 +231,11 @@ namespace Modbus.Net.Modbus
set set
{ {
_modbusType = value; _modbusType = value;
// 根据协议类型创建相应的协议实例
// Create corresponding protocol instance based on protocol type
switch (_modbusType) switch (_modbusType)
{ {
//Rtu协议 // RTU 协议 / RTU Protocol
case ModbusType.Rtu: case ModbusType.Rtu:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -164,7 +243,7 @@ namespace Modbus.Net.Modbus
: new ModbusRtuProtocol(ConnectionString, SlaveAddress, MasterAddress); : new ModbusRtuProtocol(ConnectionString, SlaveAddress, MasterAddress);
break; break;
} }
//Tcp协议 // TCP 协议 / TCP Protocol
case ModbusType.Tcp: case ModbusType.Tcp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -175,7 +254,7 @@ namespace Modbus.Net.Modbus
MasterAddress)); MasterAddress));
break; break;
} }
//Ascii协议 // ASCII 协议 / ASCII Protocol
case ModbusType.Ascii: case ModbusType.Ascii:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -183,7 +262,7 @@ namespace Modbus.Net.Modbus
: new ModbusAsciiProtocol(ConnectionString, SlaveAddress, MasterAddress); : new ModbusAsciiProtocol(ConnectionString, SlaveAddress, MasterAddress);
break; break;
} }
//Rtu协议Tcp透传 // RTU over TCP 透传 / RTU over TCP Tunneling
case ModbusType.RtuInTcp: case ModbusType.RtuInTcp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -194,7 +273,7 @@ namespace Modbus.Net.Modbus
MasterAddress)); MasterAddress));
break; break;
} }
//Ascii协议Tcp透传 // ASCII over TCP 透传 / ASCII over TCP Tunneling
case ModbusType.AsciiInTcp: case ModbusType.AsciiInTcp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -205,7 +284,7 @@ namespace Modbus.Net.Modbus
MasterAddress)); MasterAddress));
break; break;
} }
//Tcp协议Udp透传 // UDP 协议 / UDP Protocol
case ModbusType.Udp: case ModbusType.Udp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -216,7 +295,7 @@ namespace Modbus.Net.Modbus
MasterAddress)); MasterAddress));
break; break;
} }
//Rtu协议Udp透传 // RTU over UDP 透传 / RTU over UDP Tunneling
case ModbusType.RtuInUdp: case ModbusType.RtuInUdp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -227,7 +306,7 @@ namespace Modbus.Net.Modbus
MasterAddress)); MasterAddress));
break; break;
} }
//Rtu协议Udp透传 // ASCII over UDP 透传 / ASCII over UDP Tunneling
case ModbusType.AsciiInUdp: case ModbusType.AsciiInUdp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -243,28 +322,73 @@ namespace Modbus.Net.Modbus
} }
/// <summary> /// <summary>
/// 设置协议类型 /// 设置协议类型 / Set Protocol Type
/// <remarks>
/// 实现 BaseUtility 的抽象方法
/// Implements abstract method from BaseUtility
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">协议类型</param> /// <param name="connectionType">协议类型 / Protocol Type</param>
public override void SetConnectionType(int connectionType) public override void SetConnectionType(int connectionType)
{ {
ModbusType = (ModbusType)connectionType; ModbusType = (ModbusType)connectionType;
} }
/// <inheritdoc /> /// <summary>
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount) /// 读取数据 (基础方法) / Read Data (Base Method)
/// <remarks>
/// 实现 BaseUtility 的抽象方法,读取原始字节数据
/// Implements abstract method from BaseUtility, reads raw byte data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建 ReadDataModbusInputStruct / Create ReadDataModbusInputStruct</item>
/// <item>调用协议层发送接收 / Call protocol layer send/receive</item>
/// <item>返回 ReadDataModbusOutputStruct 中的数据 / Return data from ReadDataModbusOutputStruct</item>
/// <item>处理 ModbusProtocolErrorException / Handle ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="startAddress">
/// 开始地址 / Start Address
/// <remarks>
/// 格式:"4X 1", "0X 10" 等
/// Format: "4X 1", "0X 10", etc.
/// </remarks>
/// </param>
/// <param name="getByteCount">获取字节数个数 / Number of Bytes to Get</param>
/// <param name="getOriginalCount">获取原始个数 (用于位操作) / Get Original Count (for bit operations)</param>
/// <returns>
/// 接收到的 byte 数据 / Received Byte Data
/// <remarks>
/// ReturnStruct&lt;byte[]&gt; 包含:
/// ReturnStruct&lt;byte[]&gt; contains:
/// <list type="bullet">
/// <item>Datas: 读取的字节数组 / Read byte array</item>
/// <item>IsSuccess: 读取是否成功 / Read success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount, int getOriginalCount)
{ {
try try
{ {
// 创建读取输入结构 / Create read input structure
var inputStruct = new ReadDataModbusInputStruct(SlaveAddress, startAddress, var inputStruct = new ReadDataModbusInputStruct(SlaveAddress, startAddress,
(ushort)getByteCount, AddressTranslator); (ushort)getByteCount, AddressTranslator, (ushort)getOriginalCount);
// 发送接收 / Send and receive
var outputStruct = await var outputStruct = await
Wrapper.SendReceiveAsync<ReadDataModbusOutputStruct>(Wrapper[typeof(ReadDataModbusProtocol)], Wrapper.SendReceiveAsync<ReadDataModbusOutputStruct>(Wrapper[typeof(ReadDataModbusProtocol)],
inputStruct); inputStruct);
return new ReturnStruct<byte[]> return new ReturnStruct<byte[]>
{ {
Datas = outputStruct?.DataValue, Datas = outputStruct?.DataValue,
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = "" ErrorMsg = ""
}; };
@@ -282,22 +406,69 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents) /// 写入数据 (基础方法) / Write Data (Base Method)
/// <remarks>
/// 实现 BaseUtility 的抽象方法,写入对象数组
/// Implements abstract method from BaseUtility, writes object array
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建 WriteDataModbusInputStruct / Create WriteDataModbusInputStruct</item>
/// <item>调用协议层发送接收 / Call protocol layer send/receive</item>
/// <item>验证写入长度 / Verify write length</item>
/// <item>处理 ModbusProtocolErrorException / Handle ModbusProtocolErrorException</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="startAddress">
/// 开始地址 / Start Address
/// <remarks>格式:"4X 1", "0X 10" 等 / Format: "4X 1", "0X 10", etc.</remarks>
/// </param>
/// <param name="setContents">
/// 设置数据 / Set Data
/// <remarks>
/// 对象数组,如 [(ushort)100, (ushort)200]
/// Object array, e.g., [(ushort)100, (ushort)200]
/// </remarks>
/// </param>
/// <param name="setOriginalCount">
/// 设置原始长度 (用于位操作) / Set Original Length (for bit operations)
/// </param>
/// <returns>
/// 是否设置成功 / Whether Set is Successful
/// <remarks>
/// ReturnStruct&lt;bool&gt;:
/// <list type="bullet">
/// <item>Datas: true=成功false=失败 / true=success, false=failure</item>
/// <item>IsSuccess: 操作是否成功 / Operation success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents, int setOriginalCount)
{ {
try try
{ {
// 创建写入输入结构 / Create write input structure
var inputStruct = new WriteDataModbusInputStruct(SlaveAddress, startAddress, setContents, var inputStruct = new WriteDataModbusInputStruct(SlaveAddress, startAddress, setContents,
AddressTranslator, Endian); AddressTranslator, Endian, (ushort)setOriginalCount);
// 发送接收 / Send and receive
var outputStruct = await var outputStruct = await
Wrapper.SendReceiveAsync<WriteDataModbusOutputStruct>(Wrapper[typeof(WriteDataModbusProtocol)], Wrapper.SendReceiveAsync<WriteDataModbusOutputStruct>(Wrapper[typeof(WriteDataModbusProtocol)],
inputStruct); inputStruct);
// 验证写入长度 / Verify write length
var ans = outputStruct?.WriteCount * 2 == BigEndianLsbValueHelper.Instance.ObjectArrayToByteArray(setContents).Length;
return new ReturnStruct<bool>() return new ReturnStruct<bool>()
{ {
Datas = outputStruct?.WriteCount == setContents.Length, Datas = ans,
IsSuccess = outputStruct?.WriteCount == setContents.Length, IsSuccess = ans,
ErrorCode = outputStruct?.WriteCount == setContents.Length ? 0 : -2, ErrorCode = ans ? 0 : -2,
ErrorMsg = outputStruct?.WriteCount == setContents.Length ? "" : "Data length mismatch" ErrorMsg = ans ? "" : "Data length mismatch"
}; };
} }
catch (ModbusProtocolErrorException e) catch (ModbusProtocolErrorException e)
@@ -313,7 +484,14 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 获取异常状态 (功能码 07) / Get Exception Status (Function Code 07)
/// <remarks>
/// 仅用于串行通信 (RTU/ASCII)
/// For serial communication only (RTU/ASCII)
/// </remarks>
/// </summary>
/// <returns>异常状态字节 / Exception Status Byte</returns>
public async Task<ReturnStruct<byte>> GetExceptionStatusAsync() public async Task<ReturnStruct<byte>> GetExceptionStatusAsync()
{ {
try try
@@ -325,7 +503,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<byte>() return new ReturnStruct<byte>()
{ {
Datas = outputStruct.OutputData, Datas = outputStruct.OutputData,
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -343,7 +521,16 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 诊断功能 (功能码 08) / Diagnostics (Function Code 08)
/// <remarks>
/// 仅用于串行通信 (RTU/ASCII)
/// For serial communication only (RTU/ASCII)
/// </remarks>
/// </summary>
/// <param name="subFunction">子功能码 / Sub-function Code</param>
/// <param name="data">数据数组 / Data Array</param>
/// <returns>诊断数据 / Diagnostics Data</returns>
public async Task<ReturnStruct<DiagnoticsData>> GetDiagnoticsAsync(ushort subFunction, ushort[] data) public async Task<ReturnStruct<DiagnoticsData>> GetDiagnoticsAsync(ushort subFunction, ushort[] data)
{ {
try try
@@ -355,7 +542,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<DiagnoticsData>() return new ReturnStruct<DiagnoticsData>()
{ {
Datas = new DiagnoticsData() { SubFunction = outputStruct.SubFunction, Data = outputStruct.Data }, Datas = new DiagnoticsData() { SubFunction = outputStruct.SubFunction, Data = outputStruct.Data },
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -373,7 +560,14 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 获取通讯事件计数器 (功能码 11) / Get Comm Event Counter (Function Code 11)
/// <remarks>
/// 仅用于串行通信 (RTU/ASCII)
/// For serial communication only (RTU/ASCII)
/// </remarks>
/// </summary>
/// <returns>通讯事件计数器数据 / Comm Event Counter Data</returns>
public async Task<ReturnStruct<CommEventCounterData>> GetCommEventCounterAsync() public async Task<ReturnStruct<CommEventCounterData>> GetCommEventCounterAsync()
{ {
try try
@@ -385,7 +579,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<CommEventCounterData>() return new ReturnStruct<CommEventCounterData>()
{ {
Datas = new CommEventCounterData() { EventCount = outputStruct.EventCount, Status = outputStruct.Status }, Datas = new CommEventCounterData() { EventCount = outputStruct.EventCount, Status = outputStruct.Status },
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -403,7 +597,14 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 获取通讯事件日志 (功能码 12) / Get Comm Event Log (Function Code 12)
/// <remarks>
/// 仅用于串行通信 (RTU/ASCII)
/// For serial communication only (RTU/ASCII)
/// </remarks>
/// </summary>
/// <returns>通讯事件日志数据 / Comm Event Log Data</returns>
public async Task<ReturnStruct<CommEventLogData>> GetCommEventLogAsync() public async Task<ReturnStruct<CommEventLogData>> GetCommEventLogAsync()
{ {
try try
@@ -415,7 +616,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<CommEventLogData>() return new ReturnStruct<CommEventLogData>()
{ {
Datas = new CommEventLogData() { Status = outputStruct.Status, Events = outputStruct.Events }, Datas = new CommEventLogData() { Status = outputStruct.Status, Events = outputStruct.Events },
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -433,7 +634,14 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 报告从站 ID (功能码 17) / Report Slave ID (Function Code 17)
/// <remarks>
/// 仅用于串行通信 (RTU/ASCII)
/// For serial communication only (RTU/ASCII)
/// </remarks>
/// </summary>
/// <returns>从站 ID 数据 / Slave ID Data</returns>
public async Task<ReturnStruct<SlaveIdData>> GetSlaveIdAsync() public async Task<ReturnStruct<SlaveIdData>> GetSlaveIdAsync()
{ {
try try
@@ -445,7 +653,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<SlaveIdData>() return new ReturnStruct<SlaveIdData>()
{ {
Datas = new SlaveIdData() { SlaveId = outputStruct.SlaveId, IndicatorStatus = outputStruct.RunIndicatorStatus, AdditionalData = outputStruct.AdditionalData }, Datas = new SlaveIdData() { SlaveId = outputStruct.SlaveId, IndicatorStatus = outputStruct.RunIndicatorStatus, AdditionalData = outputStruct.AdditionalData },
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -463,7 +671,15 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 读文件记录 (功能码 20) / Read File Record (Function Code 20)
/// <remarks>
/// 读取从站文件记录
/// Read slave file records
/// </remarks>
/// </summary>
/// <param name="recordDefs">文件记录定义数组 / File Record Definition Array</param>
/// <returns>文件记录输出定义数组 / File Record Output Definition Array</returns>
public async Task<ReturnStruct<ReadFileRecordOutputDef[]>> GetFileRecordAsync(ReadFileRecordInputDef[] recordDefs) public async Task<ReturnStruct<ReadFileRecordOutputDef[]>> GetFileRecordAsync(ReadFileRecordInputDef[] recordDefs)
{ {
try try
@@ -475,7 +691,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<ReadFileRecordOutputDef[]>() return new ReturnStruct<ReadFileRecordOutputDef[]>()
{ {
Datas = outputStruct.RecordDefs, Datas = outputStruct.RecordDefs,
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -493,7 +709,15 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 写文件记录 (功能码 21) / Write File Record (Function Code 21)
/// <remarks>
/// 写入从站文件记录
/// Write slave file records
/// </remarks>
/// </summary>
/// <param name="recordDefs">文件记录输入定义数组 / File Record Input Definition Array</param>
/// <returns>文件记录输出定义数组 / File Record Output Definition Array</returns>
public async Task<ReturnStruct<WriteFileRecordOutputDef[]>> SetFileRecordAsync(WriteFileRecordInputDef[] recordDefs) public async Task<ReturnStruct<WriteFileRecordOutputDef[]>> SetFileRecordAsync(WriteFileRecordInputDef[] recordDefs)
{ {
try try
@@ -505,7 +729,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<WriteFileRecordOutputDef[]>() return new ReturnStruct<WriteFileRecordOutputDef[]>()
{ {
Datas = outputStruct.WriteRecords, Datas = outputStruct.WriteRecords,
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -523,7 +747,17 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 写寄存器掩码 (功能码 22) / Mask Write Register (Function Code 22)
/// <remarks>
/// 对寄存器进行 AND/OR 掩码操作
/// Perform AND/OR mask operation on register
/// </remarks>
/// </summary>
/// <param name="referenceAddress">参考地址 / Reference Address</param>
/// <param name="andMask">AND 掩码 / AND Mask</param>
/// <param name="orMask">OR 掩码 / OR Mask</param>
/// <returns>掩码寄存器数据 / Mask Register Data</returns>
public async Task<ReturnStruct<MaskRegisterData>> SetMaskRegister(ushort referenceAddress, ushort andMask, ushort orMask) public async Task<ReturnStruct<MaskRegisterData>> SetMaskRegister(ushort referenceAddress, ushort andMask, ushort orMask)
{ {
try try
@@ -535,7 +769,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<MaskRegisterData>() return new ReturnStruct<MaskRegisterData>()
{ {
Datas = new MaskRegisterData() { ReferenceAddress = outputStruct.ReferenceAddress, AndMask = outputStruct.AndMask, OrMask = outputStruct.OrMask }, Datas = new MaskRegisterData() { ReferenceAddress = outputStruct.ReferenceAddress, AndMask = outputStruct.AndMask, OrMask = outputStruct.OrMask },
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -553,7 +787,18 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 读写多个寄存器 (功能码 23) / Read/Write Multiple Registers (Function Code 23)
/// <remarks>
/// 原子操作:先读后写
/// Atomic operation: read then write
/// </remarks>
/// </summary>
/// <param name="readStartingAddress">读起始地址 / Read Starting Address</param>
/// <param name="quantityToRead">读数量 / Quantity to Read</param>
/// <param name="writeStartingAddress">写起始地址 / Write Starting Address</param>
/// <param name="writeValues">写值数组 / Write Values Array</param>
/// <returns>读取的寄存器值数组 / Read Register Values Array</returns>
public async Task<ReturnStruct<ushort[]>> GetMultipleRegister(ushort readStartingAddress, ushort quantityToRead, ushort writeStartingAddress, ushort[] writeValues) public async Task<ReturnStruct<ushort[]>> GetMultipleRegister(ushort readStartingAddress, ushort quantityToRead, ushort writeStartingAddress, ushort[] writeValues)
{ {
try try
@@ -565,7 +810,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<ushort[]>() return new ReturnStruct<ushort[]>()
{ {
Datas = outputStruct.ReadRegisterValues, Datas = outputStruct.ReadRegisterValues,
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };
@@ -583,7 +828,15 @@ namespace Modbus.Net.Modbus
} }
} }
/// <inheritdoc /> /// <summary>
/// 读 FIFO 队列 (功能码 24) / Read FIFO Queue (Function Code 24)
/// <remarks>
/// 读取从站 FIFO 队列
/// Read slave FIFO queue
/// </remarks>
/// </summary>
/// <param name="fifoPointerAddress">FIFO 指针地址 / FIFO Pointer Address</param>
/// <returns>FIFO 值寄存器数组 / FIFO Value Register Array</returns>
public async Task<ReturnStruct<ushort[]>> GetFIFOQueue(ushort fifoPointerAddress) public async Task<ReturnStruct<ushort[]>> GetFIFOQueue(ushort fifoPointerAddress)
{ {
try try
@@ -595,7 +848,7 @@ namespace Modbus.Net.Modbus
return new ReturnStruct<ushort[]>() return new ReturnStruct<ushort[]>()
{ {
Datas = outputStruct.FIFOValueRegister, Datas = outputStruct.FIFOValueRegister,
IsSuccess = true, IsSuccess = outputStruct == null ? null : true,
ErrorCode = 0, ErrorCode = 0,
ErrorMsg = null ErrorMsg = null
}; };

View File

@@ -0,0 +1,56 @@
namespace Modbus.Net.Modbus
{
/// <summary>
/// Modbus 服务器端工具类 / Modbus Server-Side Utility Class
/// <remarks>
/// 实现 Modbus 从站/服务器端功能
/// Implements Modbus slave/server-side functionality
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>响应客户端读取请求 / Respond to client read requests</item>
/// <item>响应客户端写入请求 / Respond to client write requests</item>
/// <item>维护内部数据寄存器 / Maintain internal data registers</item>
/// <item>处理 Modbus RTU/TCP 协议 / Handle Modbus RTU/TCP protocols</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Modbus 从站设备模拟 / Modbus slave device simulation</item>
/// <item>虚拟 PLC / Virtual PLC</item>
/// <item>设备仿真测试 / Device simulation testing</item>
/// <item>协议网关 / Protocol gateway</item>
/// </list>
/// </para>
/// <para>
/// 实现说明 / Implementation Notes:
/// <list type="bullet">
/// <item>继承自 BaseUtilityServer / Inherits from BaseUtilityServer</item>
/// <item>需要实现 GetServerDatasAsync 和 SetServerDatasAsync / Need to implement GetServerDatasAsync and SetServerDatasAsync</item>
/// <item>维护线圈 (0X) 和寄存器 (4X) 数据 / Maintain coil (0X) and register (4X) data</item>
/// </list>
/// </para>
/// <para>
/// TODO: 待实现 / To be implemented
/// <list type="bullet">
/// <item>内部寄存器数据存储 / Internal register data storage</item>
/// <item>读写操作处理 / Read/write operation handling</item>
/// <item>异常响应生成 / Exception response generation</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public class ModbusUtilityServer
{
// TODO: 实现 Modbus 服务器端功能
// TODO: Implement Modbus server-side functionality
// 建议的实现结构 / Suggested implementation structure:
// 1. 内部数据存储 (线圈和寄存器) / Internal data storage (coils and registers)
// 2. GetServerDatasAsync 实现 - 响应读请求 / GetServerDatasAsync implementation - respond to read requests
// 3. SetServerDatasAsync 实现 - 响应写请求 / SetServerDatasAsync implementation - respond to write requests
// 4. 异常处理 / Exception handling
// 5. 日志记录 / Logging
}
}

View File

@@ -1,62 +0,0 @@
using System;
using System.Linq;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc地址编码器
/// </summary>
public class AddressFormaterOpc<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> : AddressFormater<TAddressKey, TSubAddressKey> where TMachineKey : IEquatable<TMachineKey>
where TUnitKey : IEquatable<TUnitKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
{
/// <summary>
/// 协议构造器
/// </summary>
/// <param name="tagGeter">如何通过BaseMachine和AddressUnit构造Opc的标签</param>
/// <param name="machine">调用这个编码器的设备</param>
public AddressFormaterOpc(Func<BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey>, AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>, string> tagGeter,
BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> machine)
{
Machine = machine;
TagGeter = tagGeter;
}
/// <summary>
/// 设备
/// </summary>
public BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> Machine { get; set; }
/// <summary>
/// 标签构造器
/// (设备,地址)->不具备分隔符的标签数组
/// </summary>
protected Func<BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey>, AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>, string> TagGeter { get; set; }
/// <summary>
/// 编码地址
/// </summary>
/// <param name="area">地址所在的数据区域</param>
/// <param name="address">地址</param>
/// <returns>编码后的地址</returns>
public override string FormatAddress(string area, TAddressKey address)
{
var findAddress = Machine?.GetAddresses.FirstOrDefault(p => p.Area == area && p.Address.Equals(address));
if (findAddress == null) return null;
var ans = TagGeter(Machine, findAddress);
ans = ans.Trim().Replace(" ", "");
return ans;
}
/// <summary>
/// 编码地址
/// </summary>
/// <param name="area">地址所在的数据区域</param>
/// <param name="address">地址</param>
/// <param name="subAddress">子地址(忽略)</param>
/// <returns>编码后的地址</returns>
public override string FormatAddress(string area, TAddressKey address, TSubAddressKey subAddress)
{
return FormatAddress(area, address);
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc地址解析器
/// </summary>
public class AddressTranslatorOpc : AddressTranslator
{
/// <summary>
/// 地址转换
/// </summary>
/// <param name="address">格式化的地址</param>
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
/// <returns>翻译后的地址</returns>
public override AddressDef AddressTranslate(string address, bool isRead)
{
throw new NotImplementedException();
}
/// <summary>
/// 获取区域中的单个地址占用的字节长度
/// </summary>
/// <param name="area">区域名称</param>
/// <returns>字节长度</returns>
public override double GetAreaByteLength(string area)
{
return 1;
}
}
}

View File

@@ -1,146 +0,0 @@
using Hylasoft.Opc.Common;
using Hylasoft.Opc.Da;
using Hylasoft.Opc.Ua;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc Client Extend interface, Unified for DA and UA
/// </summary>
public interface IClientExtend : IDisposable
{
/// <summary>
/// Unified Root Node
/// </summary>
Node RootNodeBase { get; }
/// <summary>
/// Connect the client to the Opc Server
/// </summary>
void Connect();
/// <summary>
/// Read a tag
/// </summary>
/// <typeparam name="T">The type of tag to read</typeparam>
/// <param name="tag">
/// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`
/// </param>
/// <returns>The value retrieved from the Opc</returns>
ReadEvent<T> Read<T>(string tag);
/// <summary>
/// Write a value on the specified Opc tag
/// </summary>
/// <typeparam name="T">The type of tag to write on</typeparam>
/// <param name="tag">
/// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`
/// </param>
/// <param name="item"></param>
void Write<T>(string tag, T item);
/// <summary>
/// Read a tag asynchronusly
/// </summary>
Task<ReadEvent<T>> ReadAsync<T>(string tag);
/// <summary>
/// Write a value on the specified Opc tag asynchronously
/// </summary>
Task WriteAsync<T>(string tag, T item);
/// <summary>
/// Finds a node on the Opc Server asynchronously
/// </summary>
Task<Node> FindNodeAsync(string tag);
/// <summary>
/// Explore a folder on the Opc Server asynchronously
/// </summary>
Task<IEnumerable<Node>> ExploreFolderAsync(string tag);
}
/// <summary>
/// UaClient Extend
/// </summary>
public class MyDaClient : DaClient, IClientExtend
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serverUrl">OpcDa服务端Url</param>
public MyDaClient(Uri serverUrl) : base(serverUrl)
{
}
/// <summary>
/// Unified root node
/// </summary>
public Node RootNodeBase => RootNode;
}
/// <summary>
/// DaClient Extend
/// </summary>
public class MyUaClient : UaClient, IClientExtend
{
/// <summary>
/// DaClient Extend
/// </summary>
public MyUaClient(Uri serverUrl) : base(serverUrl)
{
}
/// <summary>
/// Unified root node
/// </summary>
public Node RootNodeBase => RootNode;
}
/// <summary>
/// Param input of OpcConnector
/// </summary>
public class OpcParamIn
{
/// <summary>
/// Is the action read (not is write)
/// </summary>
public bool IsRead { get; set; }
/// <summary>
/// Tag of a node
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Tag splitter of a node
/// </summary>
public char Split { get; set; }
/// <summary>
/// The value set to node(only available when IsRead is false
/// </summary>
public object SetValue { get; set; }
}
/// <summary>
/// Param output of OpcConnector
/// </summary>
public class OpcParamOut
{
/// <summary>
/// Is the action success
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Action return values
/// </summary>
public byte[] Value { get; set; }
}
}

View File

@@ -1,41 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.Opc</AssemblyName>
<RootNamespace>Modbus.Net.Opc</RootNamespace>
<PackageId>Modbus.Net.Opc</PackageId>
<Version>1.4.3</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Product>Modbus.Net.Opc</Product>
<Description>Modbus.Net Opc Implementation</Description>
<Copyright>Copyright 2023 Hangzhou Delian Science Technology Co.,Ltd.</Copyright>
<PackageProjectUrl>https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net/Modbus.Net.Opc</PackageProjectUrl>
<RepositoryUrl>https://github.com/parallelbgls/Modbus.Net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>hardware communicate protocol modbus Delian</PackageTags>
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<IncludeSymbols>True</IncludeSymbols>
<IncludeSource>True</IncludeSource>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\Modbus.Net.Opc.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\h-opc\h-opc\h-opc.csproj" />
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>

View File

@@ -1,204 +0,0 @@
using Microsoft.Extensions.Logging;
using System;
using System.Text;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc连接器
/// </summary>
public abstract class OpcConnector : BaseConnector<OpcParamIn, OpcParamOut>
{
private static readonly ILogger<OpcConnector> logger = LogProvider.CreateLogger<OpcConnector>();
/// <summary>
/// 是否正在连接
/// </summary>
protected bool _connect;
/// <summary>
/// Opc客户端
/// </summary>
protected IClientExtend Client;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">服务端url</param>
protected OpcConnector(string host)
{
ConnectionToken = host;
}
/// <summary>
/// 连接标识
/// </summary>
public override string ConnectionToken { get; }
/// <summary>
/// 是否正在连接
/// </summary>
public override bool IsConnected => _connect;
/// <summary>
/// 断开连接
/// </summary>
/// <returns></returns>
public override bool Disconnect()
{
try
{
Client?.Dispose();
Client = null;
_connect = false;
logger.LogInformation("Opc client {ConnectionToken} disconnected success", ConnectionToken);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Opc client {ConnectionToken} disconnected error", ConnectionToken);
_connect = false;
return false;
}
}
/// <inheritdoc />
protected override void ReceiveMsgThreadStart()
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override void ReceiveMsgThreadStop()
{
throw new NotImplementedException();
}
/// <inheritdoc />
protected override Task SendMsgWithoutConfirm(OpcParamIn message)
{
throw new NotImplementedException();
}
/// <summary>
/// 带返回发送数据
/// </summary>
/// <param name="message">需要发送的数据</param>
/// <returns>是否发送成功</returns>
public override async Task<OpcParamOut> SendMsgAsync(OpcParamIn message)
{
try
{
if (message.IsRead)
{
var tag = message.Tag;
if (tag != null)
{
var result = await Client.ReadAsync<object>(tag);
object resultTrans;
if (result.Value?.ToString() == "False")
{
resultTrans = (byte)0;
}
else if (result.Value?.ToString() == "True")
{
resultTrans = (byte)1;
}
else if (result.Value != null)
{
resultTrans = result.Value;
}
else
{
logger.LogError($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value null");
return new OpcParamOut
{
Success = false,
Value = Encoding.ASCII.GetBytes("NoData")
};
}
logger.LogInformation($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value {result.Value} {result.Value.GetType().FullName}");
return new OpcParamOut
{
Success = true,
Value = BigEndianLsbValueHelper.Instance.GetBytes(resultTrans, resultTrans.GetType())
};
}
logger.LogError($"Opc Machine {ConnectionToken} Read Opc tag null");
return new OpcParamOut
{
Success = false,
Value = Encoding.ASCII.GetBytes("NoData")
};
}
else
{
var tag = message.Tag;
var value = message.SetValue;
;
if (tag != null)
{
try
{
await Client.WriteAsync(tag, value);
logger.LogInformation($"Opc Machine {ConnectionToken} Write Opc tag {tag} for value {value}");
}
catch (Exception e)
{
logger.LogError(e, "Opc client {ConnectionToken} write exception", ConnectionToken);
return new OpcParamOut
{
Success = false
};
}
return new OpcParamOut
{
Success = true
};
}
return new OpcParamOut
{
Success = false
};
}
}
catch (Exception e)
{
logger.LogError(e, "Opc client {ConnectionToken} read exception", ConnectionToken);
Disconnect();
return new OpcParamOut
{
Success = false,
Value = Encoding.ASCII.GetBytes("NoData")
};
}
}
private bool Connect()
{
try
{
Client.Connect();
_connect = true;
logger.LogInformation("Opc client {ConnectionToken} connect success", ConnectionToken);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Opc client {ConnectionToken} connected failed", ConnectionToken);
_connect = false;
return false;
}
}
/// <summary>
/// 连接PLC异步
/// </summary>
/// <returns>是否连接成功</returns>
public override Task<bool> ConnectAsync()
{
return Task.FromResult(Connect());
}
}
}

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc DA连接实现
/// </summary>
public class OpcDaConnector : OpcConnector
{
/// <summary>
/// DA单例管理
/// </summary>
protected static Dictionary<string, OpcDaConnector> _instances = new Dictionary<string, OpcDaConnector>();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">Opc DA 服务地址</param>
protected OpcDaConnector(string host) : base(host)
{
}
/// <summary>
/// 根据服务地址生成DA单例
/// </summary>
/// <param name="host">Opc DA 服务地址</param>
/// <returns>Opc DA 连接器实例</returns>
public static OpcDaConnector Instance(string host)
{
if (!_instances.ContainsKey(host))
{
var connector = new OpcDaConnector(host);
_instances.Add(host, connector);
}
return _instances[host];
}
/// <inheritdoc />
public override Task<bool> ConnectAsync()
{
if (Client == null) Client = new MyDaClient(new Uri(ConnectionToken));
return base.ConnectAsync();
}
}
}

View File

@@ -1,34 +0,0 @@
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc Da协议
/// </summary>
public class OpcDaProtocol : OpcProtocol
{
private readonly string _host;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">Opc DA服务地址</param>
public OpcDaProtocol(string host)
{
_host = host;
}
/// <summary>
/// 连接设备
/// </summary>
/// <returns>是否连接成功</returns>
public override async Task<bool> ConnectAsync()
{
ProtocolLinker = new OpcDaProtocolLinker(_host);
if (!await ProtocolLinker.ConnectAsync())
return false;
return true;
}
}
}

View File

@@ -1,24 +0,0 @@
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc Da协议连接器
/// </summary>
public class OpcDaProtocolLinker : OpcProtocolLinker
{
/// <summary>
/// 构造函数
/// </summary>
public OpcDaProtocolLinker() : this(ConfigurationReader.GetValueDirect("OpcDa", "Host"))
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">Opc DA服务地址</param>
public OpcDaProtocolLinker(string host)
{
BaseConnector = OpcDaConnector.Instance(host);
}
}
}

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc设备
/// </summary>
public class OpcMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey, string, string> where TKey : IEquatable<TKey>
where TUnitKey : IEquatable<TUnitKey>
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="id">设备的ID号</param>
/// <param name="connectionType">连接类型</param>
/// <param name="connectionString">连接地址</param>
/// <param name="getAddresses">需要读写的地址</param>
/// <param name="tagSpliter">连接各个字段用的符号</param>
public OpcMachine(TKey id, OpcType connectionType, string connectionString, IEnumerable<AddressUnit<TUnitKey, string, string>> getAddresses, string tagSpliter)
: base(id, getAddresses, true)
{
BaseUtility = new OpcUtility(connectionType, connectionString);
AddressFormater = new AddressFormaterOpc<TKey, TUnitKey, string, string>((machine, unit) => { var ans = unit.Area; if (unit.Address != null) ans += tagSpliter + unit.Address; if (unit.SubAddress != null) ans += tagSpliter + unit.SubAddress; return ans; }, this);
}
}
}

View File

@@ -1,181 +0,0 @@
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc协议
/// </summary>
public abstract class OpcProtocol : BaseProtocol<OpcParamIn, OpcParamOut, ProtocolUnit<OpcParamIn, OpcParamOut>,
PipeUnit<OpcParamIn, OpcParamOut, IProtocolLinker<OpcParamIn, OpcParamOut>,
ProtocolUnit<OpcParamIn, OpcParamOut>>>
{
/// <summary>
/// 构造函数
/// </summary>
protected OpcProtocol() : base(0, 0, Endian.BigEndianLsb)
{
}
}
#region
/// <summary>
/// 读数据输入
/// </summary>
public class ReadRequestOpcInputStruct : IInputStruct
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tag">标签</param>
public ReadRequestOpcInputStruct(string tag)
{
Tag = tag;
}
/// <summary>
/// 标签
/// </summary>
public string Tag { get; }
}
/// <summary>
/// 读地址输出
/// </summary>
public class ReadRequestOpcOutputStruct : IOutputStruct
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="value">读取的数据</param>
public ReadRequestOpcOutputStruct(byte[] value)
{
GetValue = value;
}
/// <summary>
/// 读取的地址
/// </summary>
public byte[] GetValue { get; private set; }
}
/// <summary>
/// 读数据协议
/// </summary>
[SpecialProtocolUnit]
public class ReadRequestOpcProtocol : ProtocolUnit<OpcParamIn, OpcParamOut>
{
/// <summary>
/// 从对象的参数数组格式化
/// </summary>
/// <param name="message">非结构化的输入数据</param>
/// <returns>格式化后的字节流</returns>
public override OpcParamIn Format(IInputStruct message)
{
var r_message = (ReadRequestOpcInputStruct)message;
return new OpcParamIn
{
IsRead = true,
Tag = r_message.Tag,
};
}
/// <summary>
/// 把仪器返回的内容填充到输出结构中
/// </summary>
/// <param name="messageBytes">返回数据的字节流</param>
/// <param name="pos">转换标记位</param>
/// <returns>结构化的输出数据</returns>
public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos)
{
return new ReadRequestOpcOutputStruct(messageBytes.Value);
}
}
#endregion
#region
/// <summary>
/// 写数据输入
/// </summary>
public class WriteRequestOpcInputStruct : IInputStruct
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tag">标签</param>
/// <param name="setValue">写入的数据</param>
public WriteRequestOpcInputStruct(string tag, object setValue)
{
Tag = tag;
SetValue = setValue;
}
/// <summary>
/// 标签
/// </summary>
public string Tag { get; }
/// <summary>
/// 写入的数据
/// </summary>
public object SetValue { get; }
}
/// <summary>
/// 写数据输出
/// </summary>
public class WriteRequestOpcOutputStruct : IOutputStruct
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="writeResult">写入是否成功</param>
public WriteRequestOpcOutputStruct(bool writeResult)
{
WriteResult = writeResult;
}
/// <summary>
/// 写入是否成功
/// </summary>
public bool WriteResult { get; private set; }
}
/// <summary>
/// 写数据协议
/// </summary>
[SpecialProtocolUnit]
public class WriteRequestOpcProtocol : ProtocolUnit<OpcParamIn, OpcParamOut>
{
/// <summary>
/// 从对象的参数数组格式化
/// </summary>
/// <param name="message">非结构化的输入数据</param>
/// <returns>格式化后的字节流</returns>
public override OpcParamIn Format(IInputStruct message)
{
var r_message = (WriteRequestOpcInputStruct)message;
return new OpcParamIn
{
IsRead = false,
Tag = r_message.Tag,
SetValue = r_message.SetValue
};
}
/// <summary>
/// 把仪器返回的内容填充到输出结构中
/// </summary>
/// <param name="messageBytes">返回数据的字节流</param>
/// <param name="pos">转换标记位</param>
/// <returns>结构化的输出数据</returns>
public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos)
{
var ansByte = BigEndianLsbValueHelper.Instance.GetByte(messageBytes.Value, ref pos);
var ans = ansByte != 0;
return new WriteRequestOpcOutputStruct(ans);
}
}
#endregion
}

View File

@@ -1,42 +0,0 @@
using System.Text;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc协议连接器
/// </summary>
public abstract class OpcProtocolLinker : ProtocolLinker<OpcParamIn, OpcParamOut>
{
/// <summary>
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
/// </summary>
/// <param name="content">发送协议的内容</param>
/// <returns>接收协议的内容</returns>
public override async Task<OpcParamOut> SendReceiveWithoutExtAndDecAsync(OpcParamIn content)
{
//发送数据
var receiveBytes = await BaseConnector.SendMsgAsync(content);
//容错处理
var checkRight = CheckRight(receiveBytes);
return checkRight == null
? new OpcParamOut { Success = false, Value = new byte[0] }
: (!checkRight.Value ? null : receiveBytes);
//返回字符
}
/// <summary>
/// 检查接收的数据是否正确
/// </summary>
/// <param name="content">接收协议的内容</param>
/// <returns>协议是否是正确的</returns>
public override bool? CheckRight(OpcParamOut content)
{
if (content == null || !content.Success) return false;
if (content.Success && content.Value == null) { content.Value = Encoding.ASCII.GetBytes("Success"); return true; }
if (content.Value.Length == 6 && Encoding.ASCII.GetString(content.Value) == "NoData")
return null;
return base.CheckRight(content);
}
}
}

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc UA连接实现
/// </summary>
public class OpcUaConnector : OpcConnector
{
/// <summary>
/// UA单例管理
/// </summary>
protected static Dictionary<string, OpcUaConnector> _instances = new Dictionary<string, OpcUaConnector>();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">Opc UA 服务地址</param>
protected OpcUaConnector(string host) : base(host)
{
}
/// <summary>
/// 根据地址获取UA连接器单例
/// </summary>
/// <param name="host">Opc UA服务地址</param>
/// <returns>Opc UA实例</returns>
public static OpcUaConnector Instance(string host)
{
if (!_instances.ContainsKey(host))
{
var connector = new OpcUaConnector(host);
_instances.Add(host, connector);
}
return _instances[host];
}
/// <inheritdoc />
public override Task<bool> ConnectAsync()
{
if (Client == null) Client = new MyUaClient(new Uri(ConnectionToken));
return base.ConnectAsync();
}
}
}

View File

@@ -1,32 +0,0 @@
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc UA协议
/// </summary>
public class OpcUaProtocol : OpcProtocol
{
private readonly string _host;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">Opc UA服务地址</param>
public OpcUaProtocol(string host)
{
_host = host;
}
/// <summary>
/// 连接设备
/// </summary>
/// <returns>是否连接成功</returns>
public override async Task<bool> ConnectAsync()
{
ProtocolLinker = new OpcUaProtocolLinker(_host);
if (!await ProtocolLinker.ConnectAsync()) return false;
return true;
}
}
}

View File

@@ -1,24 +0,0 @@
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc UA协议连接器
/// </summary>
public class OpcUaProtocolLinker : OpcProtocolLinker
{
/// <summary>
/// 构造函数
/// </summary>
public OpcUaProtocolLinker() : this(ConfigurationReader.GetValueDirect("OpcUa", "Host"))
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="host">Opc UA服务地址</param>
public OpcUaProtocolLinker(string host)
{
BaseConnector = OpcUaConnector.Instance(host);
}
}
}

View File

@@ -1,171 +0,0 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
/// <summary>
/// Opc类型
/// </summary>
public enum OpcType
{
/// <summary>
/// DA连接
/// </summary>
Da = 0,
/// <summary>
/// UA连接
/// </summary>
Ua = 1
}
/// <summary>
/// Opc通用Api入口
/// </summary>
public class OpcUtility : BaseUtility<OpcParamIn, OpcParamOut, ProtocolUnit<OpcParamIn, OpcParamOut>,
PipeUnit<OpcParamIn, OpcParamOut, IProtocolLinker<OpcParamIn, OpcParamOut>,
ProtocolUnit<OpcParamIn, OpcParamOut>>>
{
private static readonly ILogger<OpcUtility> logger = LogProvider.CreateLogger<OpcUtility>();
private OpcType _opcType;
/// <summary>
/// 协议类型
/// </summary>
public OpcType OpcType
{
get { return _opcType; }
set
{
_opcType = value;
switch (_opcType)
{
//Da协议
case OpcType.Da:
{
Wrapper = new OpcDaProtocol(ConnectionString);
break;
}
//Ua协议
case OpcType.Ua:
{
Wrapper = new OpcUaProtocol(ConnectionString);
break;
}
}
}
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionType">连接类型</param>
/// <param name="connectionString">连接地址</param>
public OpcUtility(int connectionType, string connectionString) : base(0, 0)
{
ConnectionString = connectionString;
OpcType = (OpcType)connectionType;
AddressTranslator = new AddressTranslatorOpc();
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionType">连接类型</param>
/// <param name="connectionString">连接地址</param>
public OpcUtility(OpcType connectionType, string connectionString) : base(0, 0)
{
ConnectionString = connectionString;
OpcType = connectionType;
AddressTranslator = new AddressTranslatorOpc();
}
/// <summary>
/// 端格式(大端)
/// </summary>
public override Endian Endian => Endian.BigEndianLsb;
/// <summary>
/// 设置连接方式(Opc忽略该函数)
/// </summary>
/// <param name="connectionType">连接方式</param>
public override void SetConnectionType(int connectionType)
{
//ignore
}
/// <summary>
/// 获取数据
/// </summary>
/// <param name="startAddress">开始地址</param>
/// <param name="getByteCount">获取字节数个数</param>
/// <returns>接收到的byte数据</returns>
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount)
{
try
{
var readRequestOpcInputStruct = new ReadRequestOpcInputStruct(startAddress);
var readRequestOpcOutputStruct =
await
Wrapper.SendReceiveAsync<ReadRequestOpcOutputStruct>(Wrapper[typeof(ReadRequestOpcProtocol)],
readRequestOpcInputStruct);
return new ReturnStruct<byte[]>
{
Datas = readRequestOpcOutputStruct?.GetValue,
IsSuccess = true,
ErrorCode = 0,
ErrorMsg = ""
};
}
catch (Exception e)
{
logger.LogError(e, $"OpcUtility -> GetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<byte[]>
{
Datas = null,
IsSuccess = true,
ErrorCode = -100,
ErrorMsg = e.Message
};
}
}
/// <summary>
/// 设置数据
/// </summary>
/// <param name="startAddress">开始地址</param>
/// <param name="setContents">设置数据</param>
/// <returns>是否设置成功</returns>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents)
{
try
{
var writeRequestOpcInputStruct =
new WriteRequestOpcInputStruct(startAddress, setContents[0]);
var writeRequestOpcOutputStruct =
await
Wrapper.SendReceiveAsync<WriteRequestOpcOutputStruct>(Wrapper[typeof(WriteRequestOpcProtocol)],
writeRequestOpcInputStruct);
return new ReturnStruct<bool>
{
Datas = writeRequestOpcOutputStruct?.WriteResult == true,
IsSuccess = writeRequestOpcOutputStruct?.WriteResult == true,
ErrorCode = writeRequestOpcOutputStruct?.WriteResult == true ? 0 : 1,
ErrorMsg = writeRequestOpcOutputStruct?.WriteResult == true ? "" : "Write Failed"
};
}
catch (Exception e)
{
logger.LogError(e, $"OpcUtility -> SetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<bool>
{
Datas = false,
IsSuccess = false,
ErrorCode = -100,
ErrorMsg = e.Message
};
}
}
}
}

View File

@@ -1,12 +0,0 @@
Modbus.Net.Opc
===================
[![NuGet](https://img.shields.io/nuget/v/Modbus.Net.Opc.svg)](https://www.nuget.org/packages/Modbus.Net.Opc/)
OPC Implementation of Modbus.Net
Doc has been moved to wiki.
## Caution
Do not use this module in commercial environment.<br>
Altered h-opc uses Technosoftware.DaAeHdaSolution that will not abey for non paid commercial use.

View File

@@ -1,66 +1,220 @@
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// Siemens地址格式化Modbus.Net专用格式 /// 西门子 S7 地址格式化器类 / Siemens S7 Address Formater Classes
/// <remarks>
/// 实现西门子 PLC 地址的内部格式到字符串格式的转换
/// Implements conversion from Siemens PLC internal address format to string format
/// <para>
/// 支持的格式 / Supported Formats:
/// <list type="bullet">
/// <item><strong>Modbus.Net 格式</strong> - "DB1 0", "I 0.0", "MW100" 等 / Modbus.Net format</item>
/// <item><strong>西门子标准格式</strong> - "DB1.DBW0", "I0.0", "MW100" 等 / Siemens standard format</item>
/// </list>
/// </para>
/// <para>
/// 地址组成 / Address Components:
/// <list type="bullet">
/// <item><strong>Area</strong> - 区域标识 (DB/I/Q/M 等) / Area identifier</item>
/// <item><strong>Address</strong> - 地址偏移 / Address offset</item>
/// <item><strong>SubAddress</strong> - 子地址 (位偏移) / Sub-address (bit offset)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region Modbus.Net / Modbus.Net Format Address Formater
/// <summary>
/// Siemens 地址格式化器Modbus.Net 专用格式) / Siemens Address Formater (Modbus.Net Format)
/// <remarks>
/// 使用空格分隔的格式:区域 地址 [.子地址]
/// Uses space-separated format: Area Address [.SubAddress]
/// <para>
/// 格式化示例 / Formatting Examples:
/// <list type="bullet">
/// <item>Area="DB1", Address=0 → "DB1 0"</item>
/// <item>Area="I", Address=0, SubAddress=0 → "I 0.0"</item>
/// <item>Area="M", Address=100, SubAddress=0 → "M 100"</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Modbus.Net 内部地址表示 / Modbus.Net internal address representation</item>
/// <item>配置文件中的地址格式 / Address format in configuration files</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressFormaterSiemens : AddressFormater<int, int> public class AddressFormaterSiemens : AddressFormater<int, int>
{ {
/// <summary> /// <summary>
/// 编码地址 /// 编码地址 (无子地址) / Encode Address (without Sub-Address)
/// <remarks>
/// 将区域和地址转换为字符串格式
/// Convert area and address to string format
/// <para>
/// 格式 / Format:
/// <code>Area + " " + Address</code>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"DB1" + " " + 0 → "DB1 0"</item>
/// <item>"I" + " " + 10 → "I 10"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址所在的数据区域</param> /// <param name="area">
/// <param name="address">地址</param> /// 地址所在的数据区域 / Data Area
/// <returns>编码后的地址</returns> /// <remarks>
/// 如 "DB1", "I", "Q", "M" 等
/// e.g., "DB1", "I", "Q", "M", etc.
/// </remarks>
/// </param>
/// <param name="address">地址 / Address</param>
/// <returns>编码后的地址 / Encoded Address</returns>
public override string FormatAddress(string area, int address) public override string FormatAddress(string area, int address)
{ {
return area + " " + address; return area + " " + address;
} }
/// <summary> /// <summary>
/// 编码地址 /// 编码地址 (带子地址) / Encode Address (with Sub-Address)
/// <remarks>
/// 将区域、地址和子地址转换为字符串格式
/// Convert area, address and sub-address to string format
/// <para>
/// 格式 / Format:
/// <code>Area + " " + Address + "." + SubAddress</code>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"I" + " " + 0 + "." + 0 → "I 0.0"</item>
/// <item>"DB1" + " " + 10 + "." + 3 → "DB1 10.3"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址所在的数据区域</param> /// <param name="area">地址所在的数据区域 / Data Area</param>
/// <param name="address">地址</param> /// <param name="address">地址 / Address</param>
/// <param name="subAddress">子地址</param> /// <param name="subAddress">
/// <returns>编码后的地址</returns> /// 子地址 (位偏移) / Sub-Address (Bit Offset)
/// <remarks>
/// 范围0-7
/// Range: 0-7
/// </remarks>
/// </param>
/// <returns>编码后的地址 / Encoded Address</returns>
public override string FormatAddress(string area, int address, int subAddress) public override string FormatAddress(string area, int address, int subAddress)
{ {
return area + " " + address + "." + subAddress; return area + " " + address + "." + subAddress;
} }
} }
#endregion
#region 西 / Siemens Standard Format Address Formater
/// <summary> /// <summary>
/// Siemens地址格式化Siemens格式 /// Siemens 地址格式化器(西门子标准格式) / Siemens Address Formater (Siemens Standard Format)
/// <remarks>
/// 使用西门子标准格式:区域地址 [.子地址]
/// Uses Siemens standard format: AreaAddress [.SubAddress]
/// <para>
/// 格式化示例 / Formatting Examples:
/// <list type="bullet">
/// <item>Area="DB1", Address=0 → "DB1.DB0"</item>
/// <item>Area="DB1", Address=0, SubAddress=0 → "DB1.DBX0.0"</item>
/// <item>Area="I", Address=0, SubAddress=0 → "I0.0"</item>
/// <item>Area="M", Address=100 → "M100"</item>
/// </list>
/// </para>
/// <para>
/// DB 块特殊处理 / DB Block Special Handling:
/// <list type="bullet">
/// <item>添加 "DB" 前缀到地址 / Add "DB" prefix to address</item>
/// <item>例如DB1 块,地址 0 → "DB1.DB0"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressFormaterSimenseStandard : AddressFormater<int, int> public class AddressFormaterSimenseStandard : AddressFormater<int, int>
{ {
/// <summary> /// <summary>
/// 编码地址 /// 编码地址 (无子地址) / Encode Address (without Sub-Address)
/// <remarks>
/// 将区域和地址转换为西门子标准格式字符串
/// Convert area and address to Siemens standard format string
/// <para>
/// DB 块处理 / DB Block Handling:
/// <list type="bullet">
/// <item>格式:<code>Area + "." + "DB" + Address</code></item>
/// <item>例如:"DB1" + "." + "DB" + 0 → "DB1.DB0"</item>
/// </list>
/// </para>
/// <para>
/// 普通区域处理 / Normal Area Handling:
/// <list type="bullet">
/// <item>格式:<code>Area + Address</code></item>
/// <item>例如:"I" + 0 → "I0"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址所在的数据区域</param> /// <param name="area">地址所在的数据区域 / Data Area</param>
/// <param name="address">地址</param> /// <param name="address">地址 / Address</param>
/// <returns>编码后的地址</returns> /// <returns>编码后的地址 / Encoded Address</returns>
public override string FormatAddress(string area, int address) public override string FormatAddress(string area, int address)
{ {
// DB 块特殊处理 / DB block special handling
if (area.Length > 1 && if (area.Length > 1 &&
area.ToUpper().Substring(0, 2) == "DB") area.ToUpper().Substring(0, 2) == "DB")
return area.ToUpper() + "." + "DB" + address; return area.ToUpper() + "." + "DB" + address;
// 普通区域处理 / Normal area handling
return area.ToUpper() + address; return area.ToUpper() + address;
} }
/// <summary> /// <summary>
/// 编码地址 /// 编码地址 (带子地址) / Encode Address (with Sub-Address)
/// <remarks>
/// 将区域、地址和子地址转换为西门子标准格式字符串
/// Convert area, address and sub-address to Siemens standard format string
/// <para>
/// DB 块处理 / DB Block Handling:
/// <list type="bullet">
/// <item>格式:<code>Area + "." + "DB" + Address + "." + SubAddress</code></item>
/// <item>例如:"DB1" + "." + "DB" + 0 + "." + 0 → "DB1.DBX0.0"</item>
/// </list>
/// </para>
/// <para>
/// 普通区域处理 / Normal Area Handling:
/// <list type="bullet">
/// <item>格式:<code>Area + Address + "." + SubAddress</code></item>
/// <item>例如:"I" + 0 + "." + 0 → "I0.0"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">地址所在的数据区域</param> /// <param name="area">地址所在的数据区域 / Data Area</param>
/// <param name="address">地址</param> /// <param name="address">地址 / Address</param>
/// <param name="subAddress">子地址</param> /// <param name="subAddress">子地址 (位偏移) / Sub-Address (Bit Offset)</param>
/// <returns>编码后的地址</returns> /// <returns>编码后的地址 / Encoded Address</returns>
public override string FormatAddress(string area, int address, int subAddress) public override string FormatAddress(string area, int address, int subAddress)
{ {
// DB 块特殊处理 / DB block special handling
if (area.Length > 1 && if (area.Length > 1 &&
area.ToUpper().Substring(0, 2) == "DB") area.ToUpper().Substring(0, 2) == "DB")
return area.ToUpper() + "." + "DB" + address + "." + subAddress; return area.ToUpper() + "." + "DB" + address + "." + subAddress;
// 普通区域处理 / Normal area handling
return area.ToUpper() + address + "." + subAddress; return area.ToUpper() + address + "." + subAddress;
} }
} }
#endregion
} }

View File

@@ -1,107 +1,268 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 地址翻译器Modbus.Net格式 /// 西门子地址翻译器类 / Siemens Address Translator Classes
/// <remarks>
/// 实现西门子 PLC 地址字符串到内部地址结构的转换
/// Implements conversion from Siemens PLC address strings to internal address structure
/// <para>
/// 支持的地址格式 / Supported Address Formats:
/// <list type="bullet">
/// <item><strong>DB 块地址</strong> - "DB1.DBW0", "DB1.DBB0", "DB1.DBD0", "DB1.DBX0.0"</item>
/// <item><strong>输入映像区</strong> - "I0.0", "IB0", "IW0", "ID0"</item>
/// <item><strong>输出映像区</strong> - "Q0.0", "QB0", "QW0", "QD0"</item>
/// <item><strong>位存储区</strong> - "M0.0", "MB0", "MW0", "MD0"</item>
/// <item><strong>数据块</strong> - "DB1", "DB2" 等 / Data blocks</item>
/// </list>
/// </para>
/// <para>
/// 区域代码映射 / Area Code Mapping:
/// <list type="bullet">
/// <item><strong>S (0x04)</strong> - 系统存储区 / System memory</item>
/// <item><strong>SM (0x05)</strong> - 特殊存储区 / Special memory</item>
/// <item><strong>AI (0x06)</strong> - 模拟输入 / Analog input</item>
/// <item><strong>AQ (0x07)</strong> - 模拟输出 / Analog output</item>
/// <item><strong>C (0x1E)</strong> - 计数器 / Counter</item>
/// <item><strong>T (0x1F)</strong> - 计时器 / Timer</item>
/// <item><strong>HC (0x20)</strong> - 高速计数器 / High-speed counter</item>
/// <item><strong>I (0x81)</strong> - 输入映像区 / Input image</item>
/// <item><strong>Q (0x82)</strong> - 输出映像区 / Output image</item>
/// <item><strong>M (0x83)</strong> - 位存储区 / Memory</item>
/// <item><strong>DB (0x84)</strong> - 数据块 / Data block</item>
/// <item><strong>V (0x184)</strong> - 变量存储区 (S7-200) / Variable memory (S7-200)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region Modbus.Net / Modbus.Net Format Address Translator
/// <summary>
/// 地址翻译器Modbus.Net 格式) / Address Translator (Modbus.Net Format)
/// <remarks>
/// 使用空格分隔的格式:区域 地址
/// Uses space-separated format: Area Address
/// <para>
/// 地址格式 / Address Format:
/// <list type="bullet">
/// <item>"DB1 0" - DB1 块,偏移 0 / DB1 block, offset 0</item>
/// <item>"I 0" - 输入区,地址 0 / Input area, address 0</item>
/// <item>"Q 100" - 输出区,地址 100 / Output area, address 100</item>
/// <item>"M 50.2" - 存储区,地址 50位 2 / Memory area, address 50, bit 2</item>
/// </list>
/// </para>
/// <para>
/// DB 块特殊处理 / DB Block Special Handling:
/// <list type="bullet">
/// <item>Area = DB 编号 * 256 + 0x84</item>
/// <item>例如DB1 → Area = 1 * 256 + 0x84 = 0x0184</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressTranslatorSiemens : AddressTranslator public class AddressTranslatorSiemens : AddressTranslator
{ {
/// <summary> /// <summary>
/// 区域翻译字典 /// 区域代码翻译字典 / Area Code Translation Dictionary
/// <remarks>
/// 存储区域字符串到代码的映射
/// Stores mapping from area string to code
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<string, int> AreaCodeDictionary; protected Dictionary<string, int> AreaCodeDictionary;
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化区域代码字典
/// Initialize area code dictionary
/// </remarks>
/// </summary> /// </summary>
public AddressTranslatorSiemens() public AddressTranslatorSiemens()
{ {
AreaCodeDictionary = new Dictionary<string, int> AreaCodeDictionary = new Dictionary<string, int>
{ {
{"S", 0x04}, {"S", 0x04}, // 系统存储区 / System memory
{"SM", 0x05}, {"SM", 0x05}, // 特殊存储区 / Special memory
{"AI", 0x06}, {"AI", 0x06}, // 模拟输入 / Analog input
{"AQ", 0x07}, {"AQ", 0x07}, // 模拟输出 / Analog output
{"C", 0x1E}, {"C", 0x1E}, // 计数器 / Counter
{"T", 0x1F}, {"T", 0x1F}, // 计时器 / Timer
{"HC", 0x20}, {"HC", 0x20}, // 高速计数器 / High-speed counter
{"I", 0x81}, {"I", 0x81}, // 输入映像区 / Input image
{"Q", 0x82}, {"Q", 0x82}, // 输出映像区 / Output image
{"M", 0x83}, {"M", 0x83}, // 位存储区 / Memory
{"DB", 0x84}, {"DB", 0x84}, // 数据块 / Data block
{"V", 0x184} {"V", 0x184} // 变量存储区 (S7-200) / Variable memory (S7-200)
}; };
} }
/// <summary> /// <summary>
/// 地址转换 /// 地址转换 / Address Translate
/// <remarks>
/// 将格式化的地址字符串翻译为 AddressDef 对象
/// Translate formatted address string to AddressDef object
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>转为大写 / Convert to uppercase</item>
/// <item>按空格分割 / Split by space</item>
/// <item>提取区域 (head) 和地址 (tail) / Extract area (head) and address (tail)</item>
/// <item>处理子地址 (位偏移) / Handle sub-address (bit offset)</item>
/// <item>DB 块特殊处理 / DB block special handling</item>
/// <item>创建并返回 AddressDef / Create and return AddressDef</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="address">格式化的地址</param> /// <param name="address">
/// <param name="isRead">是否为读取,是为读取,否为写入</param> /// 格式化的地址 / Formatted Address
/// <returns>翻译后的地址</returns> /// <remarks>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"DB1 0" - DB1 块,偏移 0</item>
/// <item>"I 0.2" - 输入区,地址 0位 2</item>
/// <item>"M 100" - 存储区,地址 100</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="isRead">
/// 是否为读取 / Whether it's Read
/// <remarks>
/// true: 读取操作 / Read operation
/// false: 写入操作 / Write operation
/// </remarks>
/// </param>
/// <returns>
/// 翻译后的地址 / Translated Address
/// <remarks>
/// AddressDef 包含:
/// AddressDef contains:
/// <list type="bullet">
/// <item>AreaString: 区域字符串 / Area string</item>
/// <item>Area: 区域代码 / Area code</item>
/// <item>Address: 地址 / Address</item>
/// <item>SubAddress: 子地址 (位偏移) / Sub-address (bit offset)</item>
/// </list>
/// </remarks>
/// </returns>
public override AddressDef AddressTranslate(string address, bool isRead) public override AddressDef AddressTranslate(string address, bool isRead)
{ {
// 转为大写 / Convert to uppercase
address = address.ToUpper(); address = address.ToUpper();
// 按空格分割 / Split by space
var splitString = address.Split(' '); var splitString = address.Split(' ');
var head = splitString[0]; var head = splitString[0]; // 区域 / Area
var tail = splitString[1]; var tail = splitString[1]; // 地址 / Address
// 处理子地址 (位偏移) / Handle sub-address (bit offset)
string sub; string sub;
if (tail.Contains(".")) if (tail.Contains("."))
{ {
var splitString2 = tail.Split('.'); var splitString2 = tail.Split('.');
sub = splitString2[1]; sub = splitString2[1]; // 子地址 / Sub-address
tail = splitString2[0]; tail = splitString2[0]; // 主地址 / Main address
} }
else else
{ {
sub = "0"; sub = "0"; // 默认子地址 / Default sub-address
} }
// DB 块特殊处理 / DB block special handling
if (head.Length > 1 && head.Substring(0, 2) == "DB") if (head.Length > 1 && head.Substring(0, 2) == "DB")
{ {
head = head.Substring(2); head = head.Substring(2); // 移除 "DB" 前缀 / Remove "DB" prefix
return new AddressDef return new AddressDef
{ {
AreaString = "DB" + head, AreaString = "DB" + head, // 区域字符串 / Area string
Area = int.Parse(head) * 256 + AreaCodeDictionary["DB"], Area = int.Parse(head) * 256 + AreaCodeDictionary["DB"], // DB 块代码 / DB block code
Address = int.Parse(tail), Address = int.Parse(tail), // 地址 / Address
SubAddress = int.Parse(sub) SubAddress = int.Parse(sub) // 子地址 / Sub-address
}; };
} }
return
new AddressDef // 普通区域处理 / Normal area handling
{ return new AddressDef
AreaString = head, {
Area = AreaCodeDictionary[head], AreaString = head,
Address = int.Parse(tail), Area = AreaCodeDictionary[head],
SubAddress = int.Parse(sub) Address = int.Parse(tail),
}; SubAddress = int.Parse(sub)
};
} }
/// <summary> /// <summary>
/// 获取区域中的单个地址占用的字节长度 /// 获取区域字节长度 / Get Area Byte Length
/// <remarks>
/// 返回区域中单个地址占用的字节长度
/// Returns byte length per address in area
/// <para>
/// 西门子地址默认返回 1 字节
/// Siemens address defaults to 1 byte
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">区域名称</param> /// <param name="area">区域名称 / Area Name</param>
/// <returns>字节长度</returns> /// <returns>字节长度 / Byte Length</returns>
public override double GetAreaByteLength(string area) public override double GetAreaByteLength(string area)
{ {
return 1; return 1;
} }
} }
#endregion
#region 西 / Siemens Standard Format Address Translator
/// <summary> /// <summary>
/// 地址翻译器(Siemens格式 /// 地址翻译器(西门子标准格式) / Address Translator (Siemens Standard Format)
/// <remarks>
/// 使用西门子标准格式:区域地址.子地址
/// Uses Siemens standard format: AreaAddress.SubAddress
/// <para>
/// 支持的格式 / Supported Formats:
/// <list type="bullet">
/// <item>"DB1.DBW0" - DB1 块,字地址 0 / DB1 block, word address 0</item>
/// <item>"DB1.DBB0" - DB1 块,字节地址 0 / DB1 block, byte address 0</item>
/// <item>"DB1.DBD0" - DB1 块,双字地址 0 / DB1 block, double word address 0</item>
/// <item>"DB1.DBX0.0" - DB1 块,位地址 0.0 / DB1 block, bit address 0.0</item>
/// <item>"I0.0" - 输入区,位地址 0.0 / Input area, bit address 0.0</item>
/// <item>"QW10" - 输出区,字地址 10 / Output area, word address 10</item>
/// <item>"MW100" - 存储区,字地址 100 / Memory area, word address 100</item>
/// </list>
/// </para>
/// <para>
/// 地址类型后缀 / Address Type Suffixes:
/// <list type="bullet">
/// <item><strong>X</strong> - 位 / Bit (可选)</item>
/// <item><strong>B</strong> - 字节 / Byte</item>
/// <item><strong>W</strong> - 字 / Word</item>
/// <item><strong>D</strong> - 双字 / Double word</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class AddressTranslatorSimenseStandard : AddressTranslator public class AddressTranslatorSimenseStandard : AddressTranslator
{ {
/// <summary> /// <summary>
/// 区域翻译字典 /// 区域代码翻译字典 / Area Code Translation Dictionary
/// <remarks>
/// 存储区域字符串到代码的映射
/// Stores mapping from area string to code
/// </remarks>
/// </summary> /// </summary>
protected Dictionary<string, int> AreaCodeDictionary; protected Dictionary<string, int> AreaCodeDictionary;
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化区域代码字典
/// Initialize area code dictionary
/// </remarks>
/// </summary> /// </summary>
public AddressTranslatorSimenseStandard() public AddressTranslatorSimenseStandard()
{ {
@@ -123,21 +284,55 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 地址转换 /// 地址转换 / Address Translate
/// <remarks>
/// 将西门子标准格式地址翻译为 AddressDef 对象
/// Translate Siemens standard format address to AddressDef object
/// <para>
/// DB 块格式 / DB Block Format:
/// <list type="bullet">
/// <item>"DB1.DBW0" → AreaString="DB1", Area=0x0184, Address=0</item>
/// <item>"DB1.DBB10" → AreaString="DB1", Area=0x0184, Address=10</item>
/// </list>
/// </para>
/// <para>
/// 普通区域格式 / Normal Area Format:
/// <list type="bullet">
/// <item>"I0.0" → AreaString="I", Area=0x81, Address=0, SubAddress=0</item>
/// <item>"MW100" → AreaString="M", Area=0x83, Address=100, SubAddress=0</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="address">格式化的地址</param> /// <param name="address">
/// <param name="isRead">是否为读取,是为读取,否为写入</param> /// 格式化的地址 / Formatted Address
/// <returns>翻译后的地址</returns> /// <remarks>
/// 西门子标准格式 / Siemens standard format:
/// <list type="bullet">
/// <item>"DB1.DBW0"</item>
/// <item>"I0.0"</item>
/// <item>"MW100"</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="isRead">是否为读取 / Whether it's Read</param>
/// <returns>翻译后的地址 / Translated Address</returns>
public override AddressDef AddressTranslate(string address, bool isRead) public override AddressDef AddressTranslate(string address, bool isRead)
{ {
address = address.ToUpper(); address = address.ToUpper();
// DB 块地址处理 / DB block address handling
if (address.Substring(0, 2) == "DB") if (address.Substring(0, 2) == "DB")
{ {
var addressSplit = address.Split('.'); var addressSplit = address.Split('.');
if (addressSplit.Length != 2 && addressSplit.Length != 3) throw new FormatException(); if (addressSplit.Length != 2 && addressSplit.Length != 3) throw new FormatException();
addressSplit[0] = addressSplit[0].Substring(2);
addressSplit[0] = addressSplit[0].Substring(2); // 移除 "DB" 前缀 / Remove "DB" prefix
// 移除数据类型前缀 (DBW/DBB/DBD/DBX) / Remove data type prefix
if (addressSplit[1].Substring(0, 2) == "DB") if (addressSplit[1].Substring(0, 2) == "DB")
addressSplit[1] = addressSplit[1].Substring(2); addressSplit[1] = addressSplit[1].Substring(2);
return new AddressDef return new AddressDef
{ {
AreaString = "DB" + addressSplit[0], AreaString = "DB" + addressSplit[0],
@@ -146,13 +341,20 @@ namespace Modbus.Net.Siemens
SubAddress = addressSplit.Length == 2 ? 0 : int.Parse(addressSplit[2]) SubAddress = addressSplit.Length == 2 ? 0 : int.Parse(addressSplit[2])
}; };
} }
// 普通区域地址处理 / Normal area address handling
// 查找第一个数字的位置 / Find first digit position
var i = 0; var i = 0;
int t; int t;
while (!int.TryParse(address[i].ToString(), out t) && i < address.Length) while (!int.TryParse(address[i].ToString(), out t) && i < address.Length)
i++; i++;
if (i == 0 || i >= address.Length) throw new FormatException(); if (i == 0 || i >= address.Length) throw new FormatException();
var head = address.Substring(0, i);
var tail = address.Substring(i).Split('.'); // 分割区域和地址 / Split area and address
var head = address.Substring(0, i); // 区域 / Area
var tail = address.Substring(i).Split('.'); // 地址和子地址 / Address and sub-address
return new AddressDef return new AddressDef
{ {
AreaString = head, AreaString = head,
@@ -163,13 +365,19 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 获取区域中的单个地址占用的字节长度 /// 获取区域字节长度 / Get Area Byte Length
/// <remarks>
/// 返回区域中单个地址占用的字节长度
/// Returns byte length per address in area
/// </remarks>
/// </summary> /// </summary>
/// <param name="area">区域名称</param> /// <param name="area">区域名称 / Area Name</param>
/// <returns>字节长度</returns> /// <returns>字节长度 / Byte Length</returns>
public override double GetAreaByteLength(string area) public override double GetAreaByteLength(string area)
{ {
return 1; return 1;
} }
} }
#endregion
} }

View File

@@ -1,29 +1,118 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子Ppi协议控制器 /// 西门子 S7 协议控制器类 / Siemens S7 Protocol Controller Classes
/// <remarks>
/// 实现西门子 S7 协议的控制器,管理消息发送和响应匹配
/// Implements controllers for Siemens S7 protocol, managing message sending and response matching
/// <para>
/// 主要控制器 / Main Controllers:
/// <list type="bullet">
/// <item><strong>SiemensPpiController</strong> - PPI 串口控制器 / PPI serial controller</item>
/// <item><strong>SiemensTcpController</strong> - TCP 以太网控制器 / TCP Ethernet controller</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region PPI / PPI Protocol Controller
/// <summary>
/// 西门子 PPI 协议控制器 / Siemens PPI Protocol Controller
/// <remarks>
/// 用于 S7-200 系列 PLC 的 PPI 串口通信控制
/// Used for PPI serial communication control of S7-200 series PLC
/// <para>
/// 控制器特点 / Controller Characteristics:
/// <list type="bullet">
/// <item>继承自 FifoController / Inherits from FifoController</item>
/// <item>FIFO 顺序发送 / FIFO sequential sending</item>
/// <item>FCS 校验 / FCS checksum</item>
/// <item>支持可变长度帧 / Supports variable length frames</item>
/// </list>
/// </para>
/// <para>
/// 帧长度计算 / Frame Length Calculation:
/// <list type="bullet">
/// <item>启动字符 0x10: 固定 6 字节 / Start char 0x10: Fixed 6 bytes</item>
/// <item>启动字符 0xE5: 固定 1 字节 / Start char 0xE5: Fixed 1 byte</item>
/// <item>其他:根据长度字段计算 / Others: Calculate based on length field</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 PPI 控制器 / Create PPI controller
/// var controller = new SiemensPpiController(
/// com: "COM1",
/// slaveAddress: 2
/// );
///
/// // 添加到连接器 / Add to connector
/// connector.AddController(controller);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensPpiController : FifoController public class SiemensPpiController : FifoController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 PPI 协议控制器,配置所有参数
/// Initialize PPI protocol controller with all parameters
/// <para>
/// 配置参数 / Configuration Parameters:
/// <list type="bullet">
/// <item>FetchSleepTime: 从配置读取获取间隔 / Read from configuration</item>
/// <item>LengthCalc: PPI 帧长度计算 / PPI frame length calculation</item>
/// <item>CheckRightFunc: FCS 校验 / FCS checksum</item>
/// <item>WaitingListCount: 从配置读取等待队列长度 / Read from configuration</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口名称 / Serial Port Name
/// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// S7-200 的站地址,范围 0-126
/// S7-200 station address, range 0-126
/// </remarks>
/// </param>
public SiemensPpiController(string com, int slaveAddress) : base( public SiemensPpiController(string com, int slaveAddress) : base(
// 从配置读取获取间隔时间 / Read fetch interval time from configuration
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")), int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
// PPI 帧长度计算 / PPI frame length calculation
lengthCalc: content => lengthCalc: content =>
{ {
// 启动字符 0x10: 固定 6 字节
// Start char 0x10: Fixed 6 bytes
if (content[0] == 0x10) if (content[0] == 0x10)
return 6; return 6;
// 启动字符 0xE5: 固定 1 字节 (确认字符)
// Start char 0xE5: Fixed 1 byte (acknowledge char)
else if (content[0] == 0xE5) else if (content[0] == 0xE5)
return 1; return 1;
// 其他帧:根据长度字段计算
// Other frames: Calculate based on length field
else else
return DuplicateWithCount.GetDuplcateFunc(new List<int> { 1 }, 6)(content); return DuplicateWithCount.GetDuplcateFunc(new List<int> { 1 }, 6)(content);
}, },
// FCS 校验函数 / FCS check function
checkRightFunc: ContentCheck.FcsCheckRight, checkRightFunc: ContentCheck.FcsCheckRight,
// 从配置读取等待队列长度 / Read waiting list count from configuration
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ? waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) : int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
null null
@@ -31,23 +120,100 @@ namespace Modbus.Net.Siemens
{ } { }
} }
#endregion
#region TCP / TCP Protocol Controller
/// <summary> /// <summary>
/// 西门子Tcp协议控制器 /// 西门子 TCP 协议控制器 / Siemens TCP Protocol Controller
/// <remarks>
/// 用于 S7-1200/1500/300/400 系列 PLC 的 TCP 以太网通信控制
/// Used for TCP Ethernet communication control of S7-1200/1500/300/400 series PLC
/// <para>
/// 控制器特点 / Controller Characteristics:
/// <list type="bullet">
/// <item>继承自 MatchDirectlySendController / Inherits from MatchDirectlySendController</item>
/// <item>匹配发送,无需等待响应 / Match send, no need to wait for response</item>
/// <item>根据字节位置匹配请求响应 / Match request-response based on byte positions</item>
/// </list>
/// </para>
/// <para>
/// 匹配规则 / Matching Rules:
/// <list type="bullet">
/// <item>位置 11,12: 事务 ID / Positions 11,12: Transaction ID</item>
/// <item>用于匹配请求和响应 / Used to match request and response</item>
/// </list>
/// </para>
/// <para>
/// 帧长度计算 / Frame Length Calculation:
/// <list type="bullet">
/// <item>根据位置 2,3 的长度字段计算 / Calculate based on length field at positions 2,3</item>
/// <item>固定偏移0 字节 / Fixed offset: 0 bytes</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 TCP 控制器 / Create TCP controller
/// var controller = new SiemensTcpController(
/// ip: "192.168.1.100",
/// port: 102
/// );
///
/// // 添加到连接器 / Add to connector
/// connector.AddController(controller);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensTcpController : MatchDirectlySendController public class SiemensTcpController : MatchDirectlySendController
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 TCP 协议控制器,配置所有参数
/// Initialize TCP protocol controller with all parameters
/// <para>
/// 配置参数 / Configuration Parameters:
/// <list type="bullet">
/// <item>KeyMatches: 匹配规则 (位置 11,12 的事务 ID) / Match rules (transaction ID at positions 11,12)</item>
/// <item>LengthCalc: TCP 帧长度计算 / TCP frame length calculation</item>
/// <item>WaitingListCount: 从配置读取等待队列长度 / Read from configuration</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">ip地址</param> /// <param name="ip">
/// <param name="port">端口号</param> /// IP 地址 / IP Address
/// <remarks>
/// PLC 的 IP 地址
/// PLC IP address
/// </remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 西门子 PLC 默认端口102
/// Siemens PLC default port: 102
/// </remarks>
/// </param>
public SiemensTcpController(string ip, int port) : base( public SiemensTcpController(string ip, int port) : base(
// 匹配规则:位置 11,12 的事务 ID
// Match rules: Transaction ID at positions 11,12
new ICollection<(int, int)>[] { new List<(int, int)> { (11, 11), (12, 12) } }, new ICollection<(int, int)>[] { new List<(int, int)> { (11, 11), (12, 12) } },
// TCP 帧长度计算 / TCP frame length calculation
// 根据位置 2,3 的长度字段计算
// Calculate based on length field at positions 2,3
lengthCalc: DuplicateWithCount.GetDuplcateFunc(new List<int> { 2, 3 }, 0), lengthCalc: DuplicateWithCount.GetDuplcateFunc(new List<int> { 2, 3 }, 0),
// 从配置读取等待队列长度 / Read waiting list count from configuration
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null ? waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null ?
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount")) : int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount")) :
null null
) )
{ } { }
} }
#endregion
} }

View File

@@ -1,52 +1,254 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子设备 /// 西门子 S7 设备类 / Siemens S7 Machine Class
/// <remarks>
/// 提供西门子 S7 系列 PLC 的高级 API 封装
/// Provides high-level API encapsulation for Siemens S7 series PLC
/// <para>
/// 支持的 PLC 型号 / Supported PLC Models:
/// <list type="bullet">
/// <item><strong>S7-200</strong> - 小型 PLC / Small PLC</item>
/// <item><strong>S7-200 Smart</strong> - 增强型 S7-200 / Enhanced S7-200</item>
/// <item><strong>S7-300</strong> - 中型 PLC / Medium PLC</item>
/// <item><strong>S7-400</strong> - 大型 PLC / Large PLC</item>
/// <item><strong>S7-1200</strong> - 紧凑型 PLC / Compact PLC</item>
/// <item><strong>S7-1500</strong> - 旗舰型 PLC / Flagship PLC</item>
/// </list>
/// </para>
/// <para>
/// 连接类型 / Connection Types:
/// <list type="bullet">
/// <item><strong>SiemensType.Ppi</strong> - PPI 串口连接 (S7-200) / PPI serial connection</item>
/// <item><strong>SiemensType.Tcp</strong> - 以太网连接 (S7-1200/1500) / Ethernet connection</item>
/// </list>
/// </para>
/// <para>
/// 地址格式 / Address Formats:
/// <list type="bullet">
/// <item>"DB1.DBW0" - DB1 块,字地址 0 / DB1 block, word address 0</item>
/// <item>"DB1.DBB0" - DB1 块,字节地址 0 / DB1 block, byte address 0</item>
/// <item>"DB1.DBD0" - DB1 块,双字地址 0 / DB1 block, double word address 0</item>
/// <item>"DB1.DBX0.0" - DB1 块,位地址 0.0 / DB1 block, bit address 0.0</item>
/// <item>"I0.0" - 输入映像区,位 0 / Input image, bit 0</item>
/// <item>"Q0.0" - 输出映像区,位 0 / Output image, bit 0</item>
/// <item>"M0.0" - 位存储区,位 0 / Memory, bit 0</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 定义地址配置 / Define address configuration
/// var addresses = new List&lt;AddressUnit&gt;
/// {
/// new AddressUnit
/// {
/// Id = "1",
/// Area = "DB1",
/// Address = 0,
/// CommunicationTag = "Temperature",
/// DataType = typeof(ushort),
/// Name = "进水温度",
/// Unit = "°C"
/// },
/// new AddressUnit
/// {
/// Id = "2",
/// Area = "DB1",
/// Address = 2,
/// CommunicationTag = "Pressure",
/// DataType = typeof(ushort),
/// Name = "进水压力",
/// Unit = "MPa"
/// }
/// };
///
/// // 创建 S7-1200 设备实例 / Create S7-1200 machine instance
/// var machine = new SiemensMachine&lt;string, string&gt;(
/// id: "PLC1",
/// alias: "1#PLC",
/// connectionType: SiemensType.Tcp,
/// connectionString: "192.168.1.100:102",
/// model: SiemensMachineModel.S7_1200,
/// getAddresses: addresses,
/// keepConnect: true,
/// slaveAddress: 1,
/// masterAddress: 0,
/// src: 0x01, // 本地 TSAP / Local TSAP
/// dst: 0x01 // 远程 TSAP / Remote TSAP
/// );
///
/// // 连接 PLC / Connect to PLC
/// await machine.ConnectAsync();
///
/// // 读取数据 (按通信标签) / Read data (by communication tag)
/// var result = await machine.GetDatasAsync(MachineDataType.CommunicationTag);
/// if (result.IsSuccess)
/// {
/// double temperature = result.Datas["Temperature"].DeviceValue;
/// double pressure = result.Datas["Pressure"].DeviceValue;
/// Console.WriteLine($"温度:{temperature}°C, 压力:{pressure}MPa");
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey> where TKey : IEquatable<TKey> /// <typeparam name="TKey">
/// 设备 ID 类型 / Device ID Type
/// <remarks>
/// 通常是 string 或 int
/// Usually string or int
/// </remarks>
/// </typeparam>
/// <typeparam name="TUnitKey">
/// AddressUnit 的 ID 类型 / AddressUnit ID Type
/// <remarks>
/// 通常是 string 或 int
/// Usually string or int
/// </remarks>
/// </typeparam>
public class SiemensMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey>
where TKey : IEquatable<TKey>
where TUnitKey : IEquatable<TUnitKey> where TUnitKey : IEquatable<TUnitKey>
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (带保持连接参数) / Constructor (with Keep-Alive Parameter)
/// <remarks>
/// 初始化西门子 S7 设备实例,配置所有通信参数
/// Initialize Siemens S7 machine instance with all communication parameters
/// <para>
/// 初始化内容 / Initialization Contents:
/// <list type="bullet">
/// <item>创建 SiemensUtility 实例 / Create SiemensUtility instance</item>
/// <item>配置地址格式化器 / Configure address formater</item>
/// <item>配置地址组合器 / Configure address combiner</item>
/// <item>设置最大通信长度 100 字节 / Set max communication length 100 bytes</item>
/// </list>
/// </para>
/// <para>
/// TSAP 配置说明 / TSAP Configuration Notes:
/// <list type="bullet">
/// <item><strong>S7-200</strong>: src=本地栈号dst=远程栈号 / src=local rack, dst=remote rack</item>
/// <item><strong>S7-300/400</strong>: src=0x4b54(固定)dst=0x0300+槽号 / dst=0x0300+slot</item>
/// <item><strong>S7-1200/1500</strong>: src=0x1011(固定)dst=0x0300+槽号 / dst=0x0300+slot</item>
/// <item><strong>S7-200 Smart</strong>: src=0x0101(固定)dst=0x0101(固定) / Fixed values</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="id">设备id号</param> /// <param name="id">
/// <param name="connectionType">连接类型</param> /// 设备的 ID 号 / Device ID Number
/// <param name="connectionString">连接地址</param> /// <remarks>
/// <param name="model">设备类型</param> /// 唯一标识设备的字符串或数字
/// <param name="getAddresses">读写的地址</param> /// String or number uniquely identifying the device
/// <param name="keepConnect">是否保持连接</param> /// </remarks>
/// <param name="slaveAddress">从站号</param> /// </param>
/// <param name="masterAddress">主站号</param> /// <param name="alias">
/// <param name="src">本机模块位0到7仅200使用其它型号不要填写</param> /// 设备别名 / Device Alias
/// <param name="dst">PLC模块位0到7仅200使用其它型号不要填写</param> /// <remarks>
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model, /// 人类可读的设备名称
/// Human-readable device name
/// </remarks>
/// </param>
/// <param name="connectionType">
/// 连接类型 / Connection Type
/// <remarks>
/// SiemensType.Ppi - PPI 串口连接
/// SiemensType.Tcp - 以太网连接
/// </remarks>
/// </param>
/// <param name="connectionString">
/// 连接地址 / Connection Address
/// <remarks>
/// TCP: "192.168.1.100:102"
/// PPI: "COM1" 或 "COM1,9600,None,8,1"
/// </remarks>
/// </param>
/// <param name="model">
/// 设备类型 / Device Model
/// <remarks>
/// SiemensMachineModel 枚举值
/// SiemensMachineModel enum value
/// </remarks>
/// </param>
/// <param name="getAddresses">
/// 读写的地址 / Addresses to Read/Write
/// <remarks>
/// AddressUnit 列表,定义所有需要通信的地址
/// AddressUnit list defining all addresses to communicate
/// </remarks>
/// </param>
/// <param name="keepConnect">
/// 是否保持连接 / Whether to Keep Connection
/// <remarks>
/// true: 长连接,性能更好 / true: Long connection, better performance
/// false: 短连接,每次操作重新连接 / false: Short connection, reconnect each operation
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>PLC 的站地址 / PLC station address</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>PC/上位机的站地址 / PC/HMI station address</remarks>
/// </param>
/// <param name="src">
/// 本机模块位 / Local Module Position
/// <remarks>
/// 0 到 7仅 S7-200 使用,其它型号不要填写
/// 0 to 7, only for S7-200, leave empty for other models
/// </remarks>
/// </param>
/// <param name="dst">
/// PLC 模块位 / PLC Module Position
/// <remarks>
/// 0 到 7仅 S7-200 使用,其它型号不要填写
/// 0 to 7, only for S7-200, leave empty for other models
/// </remarks>
/// </param>
public SiemensMachine(TKey id, string alias, SiemensType connectionType, string connectionString, SiemensMachineModel model,
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0) IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0)
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress) : base(id, alias, getAddresses, keepConnect, slaveAddress, masterAddress)
{ {
// 创建 Siemens Utility 实例 / Create Siemens Utility instance
BaseUtility = new SiemensUtility(connectionType, connectionString, model, slaveAddress, masterAddress, src, dst); BaseUtility = new SiemensUtility(connectionType, connectionString, model, slaveAddress, masterAddress, src, dst);
// 配置西门子地址格式化器 / Configure Siemens address formater
AddressFormater = new AddressFormaterSiemens(); AddressFormater = new AddressFormaterSiemens();
// 配置地址组合器 (读取) / Configure address combiner (read)
// 使用连续地址组合器,最大长度 100 字节
// Use continuous address combiner, max length 100 bytes
AddressCombiner = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100); AddressCombiner = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
// 配置地址组合器 (写入) / Configure address combiner (write)
AddressCombinerSet = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100); AddressCombinerSet = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (默认保持连接=true) / Constructor (Default Keep-Alive=true)
/// <remarks>
/// 简化版本的构造函数,默认保持连接
/// Simplified constructor version with default keep-alive
/// </remarks>
/// </summary> /// </summary>
/// <param name="id">设备id号</param> /// <param name="id">设备的 ID 号 / Device ID Number</param>
/// <param name="connectionType">连接类型</param> /// <param name="alias">设备别名 / Device Alias</param>
/// <param name="connectionString">连接地址</param> /// <param name="connectionType">连接类型 / Connection Type</param>
/// <param name="model">设备类型</param> /// <param name="connectionString">连接地址 / Connection Address</param>
/// <param name="getAddresses">读写的地址</param> /// <param name="model">设备类型 / Device Model</param>
/// <param name="slaveAddress">从站号</param> /// <param name="getAddresses">读写的地址 / Addresses to Read/Write</param>
/// <param name="masterAddress">站号</param> /// <param name="slaveAddress">站号 / Slave Address</param>
/// <param name="src">本机模块位0到7仅200使用其它型号不要填写</param> /// <param name="masterAddress">主站号 / Master Address</param>
/// <param name="dst">PLC模块位0到7仅200使用其它型号不要填写</param> /// <param name="src">本机模块位 / Local Module Position</param>
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model, /// <param name="dst">PLC 模块位 / PLC Module Position</param>
public SiemensMachine(TKey id, string alias, SiemensType connectionType, string connectionString, SiemensMachineModel model,
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0) IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0)
: this(id, connectionType, connectionString, model, getAddresses, true, slaveAddress, masterAddress, src, dst) : this(id, alias, connectionType, connectionString, model, getAddresses, true, slaveAddress, masterAddress, src, dst)
{ {
} }
} }

View File

@@ -1,73 +1,222 @@
using Nito.AsyncEx; using Nito.AsyncEx;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子Ppi协议 /// 西门子 S7 PPI 协议类 / Siemens S7 PPI Protocol Class
/// <remarks>
/// 实现西门子 S7-200 系列 PLC 的 PPI (Point-to-Point Interface) 串口通信协议
/// Implements PPI (Point-to-Point Interface) serial communication protocol for Siemens S7-200 series PLC
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 RS-485 串口 / Based on RS-485 serial</item>
/// <item>主从模式 / Master-slave mode</item>
/// <item>令牌传递机制 / Token passing mechanism</item>
/// <item>波特率9.6K/19.2K/187.5K / Baud rate: 9.6K/19.2K/187.5K</item>
/// <item>适用于 S7-200 系列 / Suitable for S7-200 series</item>
/// </list>
/// </para>
/// <para>
/// 与 TCP 协议的区别 / Difference from TCP Protocol:
/// <list type="bullet">
/// <item><strong>PPI</strong> - 串口通信,无需连接建立 / Serial communication, no connection establishment</item>
/// <item><strong>TCP</strong> - 以太网通信,需要连接建立和参数协商 / Ethernet communication, requires connection establishment and parameter negotiation</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 S7-200 PPI 协议实例 / Create S7-200 PPI protocol instance
/// var protocol = new SiemensPpiProtocol(
/// "COM1", // 串口名称 / Serial port name
/// slaveAddress: 2, // S7-200 站地址
/// masterAddress: 0 // PC/上位机站地址
/// );
///
/// // 或者从配置读取 / Or read from configuration
/// var protocolFromConfig = new SiemensPpiProtocol(slaveAddress: 2, masterAddress: 0);
/// // 配置项COM:Siemens:COM = "COM1"
///
/// // 连接设备 (PPI 串口无需真正连接) / Connect to device (PPI serial doesn't need real connection)
/// await protocol.ConnectAsync();
///
/// // 读取 V 区数据 / Read V memory data
/// var inputStruct = new ReadDataSiemensInputStruct(...);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataSiemensOutputStruct&gt;(
/// protocol[typeof(ReadDataSiemensProtocol)],
/// inputStruct
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensPpiProtocol : SiemensProtocol public class SiemensPpiProtocol : SiemensProtocol
{ {
/// <summary>
/// 串口名称 / Serial Port Name
/// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </summary>
private readonly string _com; private readonly string _com;
/// <summary>
/// 异步锁 / Async Lock
/// <remarks>
/// 用于保护并发连接操作
/// Used to protect concurrent connection operations
/// </remarks>
/// </summary>
private readonly AsyncLock _lock = new AsyncLock(); private readonly AsyncLock _lock = new AsyncLock();
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取串口) / Constructor (Read COM Port from Configuration)
/// <remarks>
/// 从配置文件读取串口名称创建 PPI 协议实例
/// Create PPI protocol instance with COM port name read from configuration file
/// <para>
/// 配置项 / Configuration Item:
/// <code>COM:Siemens:COM = "COM1"</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="slaveAddress">从站号</param> /// <param name="slaveAddress">
/// <param name="masterAddress">主站号</param> /// 从站号 / Slave Address
/// <remarks>
/// S7-200 的站地址,范围 0-126
/// S7-200 station address, range 0-126
/// </remarks>
/// </param>
/// <param name="masterAddress">
/// 主站号 / Master Address
/// <remarks>
/// PC/上位机的站地址,通常为 0
/// PC/HMI station address, usually 0
/// </remarks>
/// </param>
public SiemensPpiProtocol(byte slaveAddress, byte masterAddress) public SiemensPpiProtocol(byte slaveAddress, byte masterAddress)
: this(ConfigurationReader.GetValueDirect("COM:Siemens", "COM"), slaveAddress, masterAddress) : this(ConfigurationReader.GetValueDirect("COM:Siemens", "COM"), slaveAddress, masterAddress)
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定串口) / Constructor (Specify COM Port)
/// <remarks>
/// 使用指定的串口名称创建 PPI 协议实例
/// Create PPI protocol instance with specified COM port name
/// <para>
/// 串口配置从 appsettings.json 读取
/// Serial port configuration is read from appsettings.json
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口地址</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口地址 / COM Port Address
/// <param name="masterAddress">主站号</param> /// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">从站号 / Slave Address</param>
/// <param name="masterAddress">主站号 / Master Address</param>
public SiemensPpiProtocol(string com, byte slaveAddress, byte masterAddress) public SiemensPpiProtocol(string com, byte slaveAddress, byte masterAddress)
: base(slaveAddress, masterAddress) : base(slaveAddress, masterAddress)
{ {
_com = com; _com = com;
// 创建 PPI 协议链接器
// Create PPI protocol linker
ProtocolLinker = new SiemensPpiProtocolLinker(_com, SlaveAddress); ProtocolLinker = new SiemensPpiProtocolLinker(_com, SlaveAddress);
} }
/// <summary> /// <summary>
/// 发送协议内容并接收,一般方法 /// 发送协议内容并接收 (参数数组版本) / Send Protocol Content and Receive (Parameter Array Version)
/// <remarks>
/// 发送对象数组并接收响应
/// Send object array and receive response
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>检查连接状态 / Check connection status</item>
/// <item>如果未连接,尝试连接 / If not connected, try to connect</item>
/// <item>发送数据并接收响应 / Send data and receive response</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">写入的内容,使用对象数组描述</param> /// <param name="content">写入的内容,使用对象数组描述 / Content to Write, Described Using Object Array</param>
/// <returns>从设备获取的字节流</returns> /// <returns>从设备获取的字节流 / Byte Stream Received from Device</returns>
public override async Task<PipeUnit> SendReceiveAsync(params object[] content) public override async Task<PipeUnit> SendReceiveAsync(params object[] content)
{ {
// 检查连接 / Check connection
if (ProtocolLinker == null || !ProtocolLinker.IsConnected) if (ProtocolLinker == null || !ProtocolLinker.IsConnected)
await ConnectAsync(); await ConnectAsync();
// 发送接收 / Send and receive
return await base.SendReceiveAsync(Endian, content); return await base.SendReceiveAsync(Endian, content);
} }
/// <summary> /// <summary>
/// 强行发送,不检测连接状态 /// 强行发送,不检测连接状态 / Force Send Without Checking Connection Status
/// <remarks>
/// 直接发送数据,不检测连接状态
/// Send data directly without checking connection status
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>连接建立阶段 / Connection establishment phase</item>
/// <item>特殊协议操作 / Special protocol operations</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="unit">协议核心</param> /// <param name="unit">协议核心 / Protocol Core</param>
/// <param name="content">协议的参数</param> /// <param name="content">协议的参数 / Protocol Parameters</param>
/// <returns>设备返回的信息</returns> /// <returns>设备返回的信息 / Information Returned from Device</returns>
private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content) private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content)
{ {
return await base.SendReceiveAsync(unit, content); return await base.SendReceiveAsync(unit, content);
} }
/// <summary> /// <summary>
/// 连接设备 /// 连接设备 / Connect Device
/// <remarks>
/// 打开串口并初始化 PPI 通信
/// Open serial port and initialize PPI communication
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>获取异步锁 / Acquire async lock</item>
/// <item>检查是否已连接 / Check if already connected</item>
/// <item>打开串口 / Open serial port</item>
/// <item>返回连接结果 / Return connection result</item>
/// </list>
/// </para>
/// <para>
/// PPI 串口特点 / PPI Serial Characteristics:
/// <list type="bullet">
/// <item>无需连接建立过程 / No connection establishment process</item>
/// <item>打开串口即可通信 / Can communicate once serial port is opened</item>
/// <item>主从模式,令牌传递 / Master-slave mode, token passing</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns>是否连接成功</returns> /// <returns>是否连接成功 / Whether Connection is Successful</returns>
public override async Task<bool> ConnectAsync() public override async Task<bool> ConnectAsync()
{ {
// 获取异步锁 / Acquire async lock
using (await _lock.LockAsync()) using (await _lock.LockAsync())
{ {
// 已连接 / Already connected
if (ProtocolLinker.IsConnected) return true; if (ProtocolLinker.IsConnected) return true;
// 打开串口 / Open serial port
if (!await ProtocolLinker.ConnectAsync()) return false; if (!await ProtocolLinker.ConnectAsync()) return false;
} }
return true; return true;
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.IO.Ports; using System.IO.Ports;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -6,15 +6,87 @@ using System.Threading.Tasks;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子Ppi协议连接器 /// 西门子 S7 PPI 协议连接器 / Siemens S7 PPI Protocol Linker
/// <remarks>
/// 实现西门子 S7-200 系列 PLC 的 PPI (Point-to-Point Interface) 串口通信连接器
/// Implements PPI (Point-to-Point Interface) serial communication linker for Siemens S7-200 series PLC
/// <para>
/// 协议特点 / Protocol Characteristics:
/// <list type="bullet">
/// <item>基于 RS-485 串口 / Based on RS-485 serial</item>
/// <item>主从模式 / Master-slave mode</item>
/// <item>令牌传递机制 / Token passing mechanism</item>
/// <item>波特率9.6K/19.2K/187.5K / Baud rate: 9.6K/19.2K/187.5K</item>
/// <item>适用于 S7-200 系列 / Suitable for S7-200 series</item>
/// </list>
/// </para>
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>串口连接管理 / Serial connection management</item>
/// <item>PPI 协议报文处理 / PPI protocol message handling</item>
/// <item>确认报文处理 / Acknowledge message handling</item>
/// <item>重试机制 / Retry mechanism</item>
/// <item>报文校验 / Message validation</item>
/// </list>
/// </para>
/// <para>
/// 特殊处理 / Special Handling:
/// <list type="bullet">
/// <item>0xE5 确认字符处理 / 0xE5 acknowledge character handling</item>
/// <item>0xF9 等待字符处理 / 0xF9 wait character handling</item>
/// <item>0x7C 特殊帧处理 / 0x7C special frame handling</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 PPI 连接器 / Create PPI linker
/// var linker = new SiemensPpiProtocolLinker("COM1", slaveAddress: 2);
///
/// // 连接设备 / Connect to device
/// await linker.ConnectAsync();
///
/// // 发送数据 / Send data
/// byte[] request = [0x68, 0x0B, 0x0B, 0x68, 0x02, ...];
/// byte[] response = await linker.SendReceiveAsync(request);
///
/// // 自动处理确认字符和重试
/// // Automatically handles acknowledge characters and retries
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensPpiProtocolLinker : ComProtocolLinker public class SiemensPpiProtocolLinker : ComProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 PPI 协议连接器,从配置读取校验位参数
/// Initialize PPI protocol linker, read parity parameter from configuration
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>COM:Siemens:Parity - 校验位 / Parity</item>
/// <item>默认None / Default: None</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="com">串口地址</param> /// <param name="com">
/// <param name="slaveAddress">从站号</param> /// 串口地址 / Serial Port Address
/// <remarks>
/// 如 "COM1", "COM2" 等
/// e.g., "COM1", "COM2", etc.
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站号 / Slave Address
/// <remarks>
/// S7-200 的站地址,范围 0-126
/// S7-200 station address, range 0-126
/// </remarks>
/// </param>
public SiemensPpiProtocolLinker(string com, int slaveAddress) public SiemensPpiProtocolLinker(string com, int slaveAddress)
: base(com, slaveAddress, parity: : base(com, slaveAddress, parity:
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
@@ -25,13 +97,50 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 发送协议内容并接收返回 /// 发送协议内容并接收返回 / Send Protocol Content and Receive Response
/// <remarks>
/// 发送 PPI 协议报文并接收响应,处理特殊确认字符
/// Send PPI protocol message and receive response, handle special acknowledge characters
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>扩展报文 (添加 PPI 头) / Extend message (add PPI header)</item>
/// <item>如果是 0x7C 特殊帧,发送确认报文 / If 0x7C special frame, send acknowledge message</item>
/// <item>发送扩展后的报文 / Send extended message</item>
/// <item>如果响应是 0xE5发送确认报文 / If response is 0xE5, send acknowledge message</item>
/// <item>收缩报文 (移除 PPI 头) / Reduce message (remove PPI header)</item>
/// </list>
/// </para>
/// <para>
/// 特殊字符 / Special Characters:
/// <list type="bullet">
/// <item><strong>0xE5</strong> - 单字节确认 / Single-byte acknowledge</item>
/// <item><strong>0x7C</strong> - 特殊帧标识 / Special frame identifier</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">发送的报文</param> /// <param name="content">
/// <returns>接收的报文</returns> /// 发送的报文 / Message to Send
/// <remarks>
/// PPI 协议报文
/// PPI protocol message
/// </remarks>
/// </param>
/// <returns>
/// 接收的报文 / Received Message
/// <remarks>
/// PPI 协议响应报文
/// PPI protocol response message
/// </remarks>
/// </returns>
public override async Task<byte[]> SendReceiveAsync(byte[] content) public override async Task<byte[]> SendReceiveAsync(byte[] content)
{ {
// 扩展报文 (添加 PPI 头) / Extend message (add PPI header)
var extBytes = BytesExtend(content); var extBytes = BytesExtend(content);
// 如果是 0x7C 特殊帧,发送确认报文
// If 0x7C special frame, send acknowledge message
if (extBytes[6] == 0x7c) if (extBytes[6] == 0x7c)
{ {
var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[4], content[5]); var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[4], content[5]);
@@ -39,8 +148,13 @@ namespace Modbus.Net.Siemens
await SendReceiveWithoutExtAndDecAsync( await SendReceiveWithoutExtAndDecAsync(
new ComConfirmMessageSiemensProtocol().Format(inputStruct2)); new ComConfirmMessageSiemensProtocol().Format(inputStruct2));
} }
// 发送扩展后的报文 / Send extended message
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes); var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
if (receiveBytes == null) return null; if (receiveBytes == null) return null;
// 如果响应是 0xE5发送确认报文
// If response is 0xE5, send acknowledge message
if (content.Length > 6 && receiveBytes.Length == 1 && receiveBytes[0] == 0xe5) if (content.Length > 6 && receiveBytes.Length == 1 && receiveBytes[0] == 0xe5)
{ {
var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[4], content[5]); var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[4], content[5]);
@@ -49,20 +163,50 @@ namespace Modbus.Net.Siemens
new ComConfirmMessageSiemensProtocol().Format(inputStruct2)); new ComConfirmMessageSiemensProtocol().Format(inputStruct2));
return BytesDecact(receiveBytes2); return BytesDecact(receiveBytes2);
} }
// 收缩报文 (移除 PPI 头) / Reduce message (remove PPI header)
return BytesDecact(receiveBytes); return BytesDecact(receiveBytes);
} }
/// <summary> /// <summary>
/// 发送协议内容并接收返回,不进行协议扩展和收缩 /// 发送协议内容并接收返回,不进行协议扩展和收缩 / Send Protocol Content and Receive Response Without Extension and Reduction
/// <remarks>
/// 直接发送报文并接收响应,处理 0xF9 等待字符
/// Send message directly and receive response, handle 0xF9 wait character
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>调用基类发送接收 / Call base send/receive</item>
/// <item>如果响应是 0xF9等待并重试 / If response is 0xF9, wait and retry</item>
/// <item>发送确认报文 / Send acknowledge message</item>
/// <item>重复直到收到有效响应 / Repeat until valid response received</item>
/// </list>
/// </para>
/// <para>
/// 特殊字符 / Special Characters:
/// <list type="bullet">
/// <item><strong>0xF9</strong> - 等待字符,需要重试 / Wait character, needs retry</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">发送的报文</param> /// <param name="content">
/// <returns>接收的报文</returns> /// 发送的报文 / Message to Send
/// <remarks>PPI 协议报文 / PPI protocol message</remarks>
/// </param>
/// <returns>
/// 接收的报文 / Received Message
/// <remarks>PPI 协议响应报文 / PPI protocol response message</remarks>
/// </returns>
public override async Task<byte[]> SendReceiveWithoutExtAndDecAsync(byte[] content) public override async Task<byte[]> SendReceiveWithoutExtAndDecAsync(byte[] content)
{ {
var ans = await base.SendReceiveWithoutExtAndDecAsync(content); var ans = await base.SendReceiveWithoutExtAndDecAsync(content);
// 处理 0xF9 等待字符 / Handle 0xF9 wait character
while (ans?.Length == 1 && ans[0] == 0xf9) while (ans?.Length == 1 && ans[0] == 0xf9)
{ {
Thread.Sleep(500); Thread.Sleep(500); // 等待 500ms / Wait 500ms
if (content.Length <= 6) if (content.Length <= 6)
{ {
var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[1], content[2]); var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[1], content[2]);
@@ -82,18 +226,64 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 校验报文 /// 校验报文 / Validate Message
/// <remarks>
/// 校验从 S7-200 PLC 返回的 PPI 协议报文
/// Validate PPI protocol message returned from S7-200 PLC
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (串口连接状态) / Call base validation (serial connection status)</item>
/// <item>检查单字节 0xE5 确认 / Check single-byte 0xE5 acknowledge</item>
/// <item>检查 6 字节短帧 / Check 6-byte short frame</item>
/// <item>检查结束符 0x16 / Check terminator 0x16</item>
/// <item>检查长度字段 / Check length field</item>
/// </list>
/// </para>
/// <para>
/// PPI 帧格式 / PPI Frame Format:
/// <list type="bullet">
/// <item>单字节0xE5 (确认) / Single byte: 0xE5 (acknowledge)</item>
/// <item>短帧6 字节 / Short frame: 6 bytes</item>
/// <item>长帧:[长度][长度][数据...][0x16] / Long frame: [length][length][data...][0x16]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的信息</param> /// <param name="content">
/// <returns>报文是否正确</returns> /// 设备返回的信息 / Information Returned from Device
/// <remarks>
/// PPI 协议报文
/// PPI protocol message
/// </remarks>
/// </param>
/// <returns>
/// 报文是否正确 / Whether Message is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 报文正确 / Message correct</item>
/// <item>false: 报文错误 / Message error</item>
/// </list>
/// </remarks>
/// </returns>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
// 基类校验 (串口连接状态) / Base validation (serial connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return false;
// 单字节 0xE5 确认 / Single-byte 0xE5 acknowledge
if (content.Length == 1 && content[0] == 0xe5) if (content.Length == 1 && content[0] == 0xe5)
return true; return true;
// 6 字节短帧 / 6-byte short frame
if (content.Length == 6 && content[3] == 0) return true; if (content.Length == 6 && content[3] == 0) return true;
// 检查结束符 0x16 / Check terminator 0x16
if (content[content.Length - 1] != 0x16) return false; if (content[content.Length - 1] != 0x16) return false;
// 检查长度字段 / Check length field
if (content[1] != content.Length - 6) return false; if (content[1] != content.Length - 6) return false;
return true; return true;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,287 @@
using System; using System;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子Tcp协议扩展 /// 西门子 S7 协议字节伸缩类 / Siemens S7 Protocol Bytes Extend Classes
/// <remarks>
/// 实现西门子 S7 协议的字节扩展和收缩功能
/// Implements bytes extend and reduce functionality for Siemens S7 protocol
/// <para>
/// 主要类 / Main Classes:
/// <list type="bullet">
/// <item><strong>SiemensTcpProtocolLinkerBytesExtend</strong> - TCP 协议扩展 / TCP protocol extension</item>
/// <item><strong>SiemensPpiProtocolLinkerBytesExtend</strong> - PPI 协议扩展 / PPI protocol extension</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
#region TCP / TCP Protocol Bytes Extend
/// <summary>
/// 西门子 TCP 协议字节扩展 / Siemens TCP Protocol Bytes Extend
/// <remarks>
/// 实现西门子 S7 TCP 协议的字节扩展和收缩功能
/// Implements bytes extend and reduce functionality for Siemens S7 TCP protocol
/// <para>
/// 扩展格式 / Extension Format:
/// <code>
/// [0x03][0x00][长度 (2)][0x02][0xF0][0x80][数据...]
/// │ │ │ │ │ │ │
/// └─ ISO 传输协议标识
/// └─ 总长度 (包含后面所有字节)
/// └─ COTP 头
/// └─ 数据部分
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var bytesExtend = new SiemensTcpProtocolLinkerBytesExtend();
///
/// // 扩展 (发送前) / Extend (before sending)
/// byte[] rawData = [0x01, 0x02, 0x03, 0x04];
/// byte[] extendedData = bytesExtend.BytesExtend(rawData);
/// // 结果:[0x03, 0x00, 0x00, 0x0B, 0x02, 0xF0, 0x80, 0x01, 0x02, 0x03, 0x04]
/// ///
/// // 收缩 (接收后) / Reduce (after receiving)
/// byte[] receivedData = [0x03, 0x00, 0x00, 0x0B, 0x02, 0xF0, 0x80, 0x01, 0x02, 0x03, 0x04];
/// byte[] reducedData = bytesExtend.BytesDecact(receivedData);
/// // 结果:[0x01, 0x02, 0x03, 0x04] // 移除 7 字节头
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensTcpProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]> public class SiemensTcpProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
{ {
/// <summary> /// <summary>
/// 协议扩展,协议内容发送前调用 /// 协议扩展 (发送前调用) / Protocol Extend (Called Before Sending)
/// <remarks>
/// 在 S7 TCP 协议数据前添加 ISO 传输头和 COTP 头
/// Add ISO transport header and COTP header before S7 TCP protocol data
/// <para>
/// 添加的头部 / Added Header (7 bytes):
/// <list type="bullet">
/// <item><strong>0x03</strong> - ISO 传输协议标识 / ISO transport protocol identifier</item>
/// <item><strong>0x00</strong> - 保留 / Reserved</item>
/// <item><strong>长度 (2 字节)</strong> - 总长度 (包含后面所有字节) / Total length (includes all following bytes)</item>
/// <item><strong>0x02</strong> - COTP 头长度 / COTP header length</item>
/// <item><strong>0xF0</strong> - COTP 数据类型 (数据) / COTP data type (data)</item>
/// <item><strong>0x80</strong> - COTP 控制位 / COTP control bits</item>
/// </list>
/// </para>
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>复制 7 字节固定头 / Copy 7-byte fixed header</item>
/// <item>计算总长度并写入 / Calculate total length and write</item>
/// <item>返回扩展后的数据 / Return extended data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">扩展前的原始协议内容</param> /// <param name="content">
/// <returns>扩展后的协议内容</returns> /// 扩展前的原始协议内容 / Original Protocol Content Before Extension
/// <remarks>
/// S7 协议数据
/// S7 protocol data
/// </remarks>
/// </param>
/// <returns>
/// 扩展后的协议内容 / Extended Protocol Content
/// <remarks>
/// 添加了 ISO/COTP 头的完整数据
/// Complete data with ISO/COTP header added
/// </remarks>
/// </returns>
public byte[] BytesExtend(byte[] content) public byte[] BytesExtend(byte[] content)
{ {
// 复制 7 字节固定头 / Copy 7-byte fixed header
Array.Copy(new byte[] { 0x03, 0x00, 0x00, 0x00, 0x02, 0xf0, 0x80 }, 0, content, 0, 7); Array.Copy(new byte[] { 0x03, 0x00, 0x00, 0x00, 0x02, 0xf0, 0x80 }, 0, content, 0, 7);
// 计算总长度并写入 (大端格式) / Calculate total length and write (big-endian)
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes((ushort)content.Length), 0, content, 2, 2); Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes((ushort)content.Length), 0, content, 2, 2);
return content; return content;
} }
/// <summary> /// <summary>
/// 协议收缩,协议内容接收后调用 /// 协议收缩 (接收后调用) / Protocol Reduce (Called After Receiving)
/// <remarks>
/// 移除 S7 TCP 协议数据的 ISO 传输头和 COTP 头
/// Remove ISO transport header and COTP header from S7 TCP protocol data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建新数组,长度 = 原长度 - 7 / Create new array, length = original length - 7</item>
/// <item>从第 8 个字节开始复制 / Copy starting from 8th byte</item>
/// <item>返回纯 S7 协议数据 / Return pure S7 protocol data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">收缩前的完整协议内容</param> /// <param name="content">
/// <returns>收缩后的协议内容</returns> /// 收缩前的完整协议内容 / Complete Protocol Content Before Reduction
/// <remarks>
/// 包含 ISO/COTP 头的完整数据
/// Complete data with ISO/COTP header
/// </remarks>
/// </param>
/// <returns>
/// 收缩后的协议内容 / Reduced Protocol Content
/// <remarks>
/// 移除 ISO/COTP 头后的 S7 协议数据
/// S7 protocol data with ISO/COTP header removed
/// </remarks>
/// </returns>
public byte[] BytesDecact(byte[] content) public byte[] BytesDecact(byte[] content)
{ {
// 移除 7 字节头 / Remove 7-byte header
var newContent = new byte[content.Length - 7]; var newContent = new byte[content.Length - 7];
Array.Copy(content, 7, newContent, 0, newContent.Length); Array.Copy(content, 7, newContent, 0, newContent.Length);
return newContent; return newContent;
} }
} }
#endregion
#region PPI / PPI Protocol Bytes Extend
/// <summary> /// <summary>
/// 西门子Ppi协议扩展 /// 西门子 PPI 协议字节扩展 / Siemens PPI Protocol Bytes Extend
/// <remarks>
/// 实现西门子 S7 PPI 协议的字节扩展和收缩功能
/// Implements bytes extend and reduce functionality for Siemens S7 PPI protocol
/// <para>
/// PPI 帧格式 / PPI Frame Format:
/// <code>
/// [0x68][长度][长度][0x68][控制][地址][数据...][校验][0x16]
/// │ │ │ │ │ │ │ │ │
/// └─ 起始符 └─ 长度重复 └─ 起始符 └─ 控制 └─ 地址 └─ 数据 └─ 校验 └─ 结束符
/// </code>
/// </para>
/// <para>
/// 校验计算 / Checksum Calculation:
/// <list type="bullet">
/// <item>从第 5 个字节 (控制) 开始累加到倒数第 2 个字节 / Sum from byte 5 (control) to second-to-last byte</item>
/// <item>结果模 256 / Result mod 256</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensPpiProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]> public class SiemensPpiProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
{ {
/// <summary> /// <summary>
/// 协议扩展,协议内容发送前调用 /// 协议扩展 (发送前调用) / Protocol Extend (Called Before Sending)
/// <remarks>
/// 在 PPI 协议数据前添加帧头,后添加校验和结束符
/// Add frame header before PPI protocol data, add checksum and terminator after
/// <para>
/// 添加的头部 / Added Header (4 bytes):
/// <list type="bullet">
/// <item><strong>0x68</strong> - 起始符 / Start character</item>
/// <item><strong>长度</strong> - 数据长度 -4 / Data length -4</item>
/// <item><strong>长度</strong> - 重复 / Repeat</item>
/// <item><strong>0x68</strong> - 起始符 / Start character</item>
/// </list>
/// </para>
/// <para>
/// 添加的尾部 / Added Tail (2 bytes):
/// <list type="bullet">
/// <item><strong>校验和</strong> - 从控制字节累加 / Checksum - sum from control byte</item>
/// <item><strong>0x16</strong> - 结束符 / End character</item>
/// </list>
/// </para>
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建新数组,长度 = 原长度 + 2 / Create new array, length = original length + 2</item>
/// <item>复制原始数据 / Copy original data</item>
/// <item>添加 4 字节帧头 / Add 4-byte frame header</item>
/// <item>计算校验和 / Calculate checksum</item>
/// <item>添加校验和和结束符 / Add checksum and terminator</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">扩展前的原始协议内容</param> /// <param name="content">
/// <returns>扩展后的协议内容</returns> /// 扩展前的原始协议内容 / Original Protocol Content Before Extension
/// <remarks>
/// PPI 协议数据
/// PPI protocol data
/// </remarks>
/// </param>
/// <returns>
/// 扩展后的协议内容 / Extended Protocol Content
/// <remarks>
/// 添加了帧头、校验和结束符的完整 PPI 帧
/// Complete PPI frame with header, checksum and terminator added
/// </remarks>
/// </returns>
public byte[] BytesExtend(byte[] content) public byte[] BytesExtend(byte[] content)
{ {
// 创建新数组,长度 = 原长度 + 2 (校验 + 结束符)
// Create new array, length = original length + 2 (checksum + terminator)
var newContent = new byte[content.Length + 2]; var newContent = new byte[content.Length + 2];
// 复制原始数据 / Copy original data
Array.Copy(content, 0, newContent, 0, content.Length); Array.Copy(content, 0, newContent, 0, content.Length);
Array.Copy(new byte[] { 0x68, (byte)(content.Length - 4), (byte)(content.Length - 4), 0x68 }, 0, newContent,
0, 4); // 添加 4 字节帧头 / Add 4-byte frame header
Array.Copy(new byte[] { 0x68, (byte)(content.Length - 4), (byte)(content.Length - 4), 0x68 }, 0, newContent, 0, 4);
// 计算校验和 / Calculate checksum
var check = 0; var check = 0;
for (var i = 4; i < newContent.Length - 2; i++) for (var i = 4; i < newContent.Length - 2; i++)
check += newContent[i]; check += newContent[i];
check = check % 256; check = check % 256;
// 添加校验和和结束符 / Add checksum and terminator
newContent[newContent.Length - 2] = (byte)check; newContent[newContent.Length - 2] = (byte)check;
newContent[newContent.Length - 1] = 0x16; newContent[newContent.Length - 1] = 0x16;
return newContent; return newContent;
} }
/// <summary> /// <summary>
/// 协议收缩,协议内容接收后调用 /// 协议收缩 (接收后调用) / Protocol Reduce (Called After Receiving)
/// <remarks>
/// 移除 PPI 协议数据的帧头、校验和结束符
/// Remove frame header, checksum and terminator from PPI protocol data
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建新数组,长度 = 原长度 - 9 / Create new array, length = original length - 9</item>
/// <item>从第 8 个字节开始复制 / Copy starting from 8th byte</item>
/// <item>返回纯 PPI 协议数据 / Return pure PPI protocol data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">收缩前的完整协议内容</param> /// <param name="content">
/// <returns>收缩后的协议内容</returns> /// 收缩前的完整协议内容 / Complete Protocol Content Before Reduction
/// <remarks>
/// 完整的 PPI 帧
/// Complete PPI frame
/// </remarks>
/// </param>
/// <returns>
/// 收缩后的协议内容 / Reduced Protocol Content
/// <remarks>
/// 移除帧头、校验和结束符后的 PPI 协议数据
/// PPI protocol data with header, checksum and terminator removed
/// </remarks>
/// </returns>
public byte[] BytesDecact(byte[] content) public byte[] BytesDecact(byte[] content)
{ {
// 移除 9 字节 (4 字节头 + 2 字节校验 + 1 字节结束符 + 2 字节其他)
// Remove 9 bytes (4-byte header + 2-byte checksum + 1-byte terminator + 2-byte other)
var newContent = new byte[content.Length - 9]; var newContent = new byte[content.Length - 9];
Array.Copy(content, 7, newContent, 0, newContent.Length); Array.Copy(content, 7, newContent, 0, newContent.Length);
return newContent; return newContent;
} }
} }
#endregion
} }

View File

@@ -1,7 +1,57 @@
/*namespace Modbus.Net.Siemens /*
namespace Modbus.Net.Siemens
{ {
/// <summary>
/// 西门子 TOD (Time of Day) 时钟状态结构 / Siemens TOD (Time of Day) Clock Status Structure
/// <remarks>
/// 定义西门子 PLC 实时时钟的状态位
/// Defines status bits for Siemens PLC real-time clock
/// <para>
/// 状态位说明 / Status Bit Description:
/// <list type="bullet">
/// <item><strong>KV (Bit 15)</strong> - 时钟有效标志 / Clock valid flag</item>
/// <item><strong>K0_4 (Bits 6-10)</strong> - 精度等级 / Accuracy class</item>
/// <item><strong>ZNA (Bit 5)</strong> - 时区不可用 / Time zone not available</item>
/// <item><strong>UA (Bits 3-4)</strong> - 更新区域 / Update area</item>
/// <item><strong>UZS (Bit 2)</strong> - 夏令时标志 / Daylight saving time flag</item>
/// <item><strong>ESY (Bit 1)</strong> - 能量保存标志 / Energy save flag</item>
/// <item><strong>SYA (Bit 0)</strong> - 同步激活标志 / Synchronization active flag</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 读取 TOD 时钟状态 / Read TOD clock status
/// TodClockStatus status = new TodClockStatus { TodValue = 0x8000 };
///
/// // 检查时钟是否有效 / Check if clock is valid
/// bool isValid = status.KV; // true
///
/// // 设置时钟有效 / Set clock valid
/// status.KV = true;
/// </code>
/// </para>
/// <para>
/// 注意 / Note:
/// <list type="bullet">
/// <item>此代码当前被注释掉 / This code is currently commented out</item>
/// <item>需要根据实际项目需求启用 / Need to enable based on actual project requirements</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public struct TodClockStatus public struct TodClockStatus
{ {
/// <summary>
/// KV - 时钟有效标志 / Clock Valid Flag
/// <remarks>
/// Bit 15
/// <list type="bullet">
/// <item>true: 时钟时间有效 / Clock time is valid</item>
/// <item>false: 时钟时间无效 / Clock time is invalid</item>
/// </list>
/// </remarks>
/// </summary>
public bool KV public bool KV
{ {
get get
@@ -12,6 +62,14 @@
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 15, value); } set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 15, value); }
} }
/// <summary>
/// K0_4 - 精度等级 / Accuracy Class
/// <remarks>
/// Bits 6-10
/// 表示时钟的精度等级
/// Represents clock accuracy class
/// </remarks>
/// </summary>
public byte K0_4 public byte K0_4
{ {
get get
@@ -29,6 +87,16 @@
} }
} }
/// <summary>
/// ZNA - 时区不可用标志 / Time Zone Not Available Flag
/// <remarks>
/// Bit 5
/// <list type="bullet">
/// <item>true: 时区信息不可用 / Time zone information not available</item>
/// <item>false: 时区信息可用 / Time zone information available</item>
/// </list>
/// </remarks>
/// </summary>
public bool ZNA public bool ZNA
{ {
get get
@@ -39,6 +107,14 @@
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 5, value); } set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 5, value); }
} }
/// <summary>
/// UA - 更新区域 / Update Area
/// <remarks>
/// Bits 3-4
/// 表示时钟更新区域
/// Represents clock update area
/// </remarks>
/// </summary>
public byte UA public byte UA
{ {
get get
@@ -55,6 +131,17 @@
TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 4, value / 2 >= 1); TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 4, value / 2 >= 1);
} }
} }
/// <summary>
/// UZS - 夏令时标志 / Daylight Saving Time Flag
/// <remarks>
/// Bit 2
/// <list type="bullet">
/// <item>true: 夏令时有效 / Daylight saving time active</item>
/// <item>false: 标准时间 / Standard time</item>
/// </list>
/// </remarks>
/// </summary>
public bool UZS public bool UZS
{ {
get get
@@ -65,6 +152,14 @@
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 2, value); } set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 2, value); }
} }
/// <summary>
/// ESY - 能量保存标志 / Energy Save Flag
/// <remarks>
/// Bit 1
/// 用于能量保存模式
/// Used for energy save mode
/// </remarks>
/// </summary>
public bool ESY public bool ESY
{ {
get get
@@ -75,6 +170,16 @@
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 1, value); } set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 1, value); }
} }
/// <summary>
/// SYA - 同步激活标志 / Synchronization Active Flag
/// <remarks>
/// Bit 0
/// <list type="bullet">
/// <item>true: 同步激活 / Synchronization active</item>
/// <item>false: 同步未激活 / Synchronization not active</item>
/// </list>
/// </remarks>
/// </summary>
public bool SYA public bool SYA
{ {
get get
@@ -85,8 +190,14 @@
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 0, value); } set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 0, value); }
} }
/// <summary>
/// TOD 值 / TOD Value
/// <remarks>
/// 16 位时钟状态值
/// 16-bit clock status value
/// </remarks>
/// </summary>
public ushort TodValue { get; set; } public ushort TodValue { get; set; }
} }
} }
*/ */

View File

@@ -1,33 +1,185 @@
using Nito.AsyncEx; using Nito.AsyncEx;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子Tcp协议 /// 西门子 S7 TCP 协议类 / Siemens S7 TCP Protocol Class
/// <remarks>
/// 实现西门子 S7 系列 PLC 的以太网通信协议 (ISO-on-TCP)
/// Implements Ethernet communication protocol (ISO-on-TCP) for Siemens S7 series PLC
/// <para>
/// 支持的 PLC 型号 / Supported PLC Models:
/// <list type="bullet">
/// <item><strong>S7-1200</strong> - 紧凑型 PLC / Compact PLC</item>
/// <item><strong>S7-1500</strong> - 旗舰型 PLC / Flagship PLC</item>
/// <item><strong>S7-300</strong> - 中型 PLC (带以太网模块) / Medium PLC (with Ethernet module)</item>
/// <item><strong>S7-400</strong> - 大型 PLC (带以太网模块) / Large PLC (with Ethernet module)</item>
/// </list>
/// </para>
/// <para>
/// 连接参数 / Connection Parameters:
/// <list type="bullet">
/// <item><strong>TDPU Size</strong> - DPU 大小标识 / DPU size identifier</item>
/// <item><strong>TSAP Src</strong> - 本地 TSAP 地址 / Local TSAP address</item>
/// <item><strong>TSAP Dst</strong> - 远程 TSAP 地址 / Remote TSAP address</item>
/// <item><strong>Max Calling</strong> - 最大调用连接数 / Max calling connections</item>
/// <item><strong>Max Called</strong> - 最大被调用连接数 / Max called connections</item>
/// <item><strong>Max PDU</strong> - 最大 PDU 长度 / Max PDU length</item>
/// </list>
/// </para>
/// <para>
/// 连接流程 / Connection Flow:
/// <list type="number">
/// <item>建立 TCP 连接 / Establish TCP connection</item>
/// <item>发送 CR (Connection Request) / Send CR (Connection Request)</item>
/// <item>接收 CC (Connection Confirm) / Receive CC (Connection Confirm)</item>
/// <item>建立 S7 连接引用 / Establish S7 connection reference</item>
/// <item>协商 PDU 参数 / Negotiate PDU parameters</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建 S7-1200 TCP 协议实例 / Create S7-1200 TCP protocol instance
/// var protocol = new SiemensTcpProtocol(
/// tdpuSize: 0x0a, // S7-1200 DPU 大小
/// tsapSrc: 0x1011, // 本地 TSAP
/// tsapDst: 0x0301, // 远程 TSAP (槽号 1)
/// maxCalling: 0x0003, // 最大调用连接
/// maxCalled: 0x0003, // 最大被调用连接
/// maxPdu: 0x0100, // 最大 PDU 长度 (256 字节)
/// ip: "192.168.1.100",
/// port: 102 // 西门子默认端口
/// );
///
/// // 连接 PLC / Connect to PLC
/// bool connected = await protocol.ConnectAsync();
/// if (connected)
/// {
/// // 读取 DB 块数据 / Read DB block data
/// var inputStruct = new ReadDataSiemensInputStruct(...);
/// var outputStruct = await protocol.SendReceiveAsync&lt;ReadDataSiemensOutputStruct&gt;(
/// protocol[typeof(ReadDataSiemensProtocol)],
/// inputStruct
/// );
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensTcpProtocol : SiemensProtocol public class SiemensTcpProtocol : SiemensProtocol
{ {
/// <summary>
/// IP 地址 / IP Address
/// <remarks>
/// PLC 的 IP 地址
/// PLC IP address
/// </remarks>
/// </summary>
private readonly string _ip; private readonly string _ip;
/// <summary>
/// 最大被调用连接数 / Max Called Connections
/// <remarks>
/// PLC 允许的最大并发连接数 (被调用方)
/// Maximum concurrent connections allowed by PLC (called party)
/// </remarks>
/// </summary>
private readonly ushort _maxCalled; private readonly ushort _maxCalled;
/// <summary>
/// 最大调用连接数 / Max Calling Connections
/// <remarks>
/// PLC 允许的最大并发连接数 (调用方)
/// Maximum concurrent connections allowed by PLC (calling party)
/// </remarks>
/// </summary>
private readonly ushort _maxCalling; private readonly ushort _maxCalling;
/// <summary>
/// 最大 PDU 长度 / Max PDU Length
/// <remarks>
/// 协议数据单元最大长度
/// Maximum Protocol Data Unit length
/// </remarks>
/// </summary>
private readonly ushort _maxPdu; private readonly ushort _maxPdu;
/// <summary>
/// 端口号 / Port Number
/// <remarks>
/// 西门子 PLC 默认端口102
/// Siemens PLC default port: 102
/// </remarks>
/// </summary>
private readonly int _port; private readonly int _port;
/// <summary>
/// 本地 TSAP 地址 / Local TSAP Address
/// <remarks>
/// 传输服务访问点地址 (本地)
/// Transport Service Access Point address (local)
/// </remarks>
/// </summary>
private readonly ushort _taspSrc; private readonly ushort _taspSrc;
/// <summary>
/// DPU 大小 / DPU Size
/// <remarks>
/// 数据协议单元大小标识
/// Data Protocol Unit size identifier
/// </remarks>
/// </summary>
private readonly byte _tdpuSize; private readonly byte _tdpuSize;
/// <summary>
/// 远程 TSAP 地址 / Remote TSAP Address
/// <remarks>
/// 传输服务访问点地址 (远程/PLC 侧)
/// Transport Service Access Point address (remote/PLC side)
/// </remarks>
/// </summary>
private readonly ushort _tsapDst; private readonly ushort _tsapDst;
/// <summary>
/// 连接尝试计数 / Connection Try Count
/// <remarks>
/// 记录连接尝试次数,超过 10 次放弃
/// Records connection try count, give up after 10 tries
/// </remarks>
/// </summary>
private int _connectTryCount; private int _connectTryCount;
/// <summary>
/// 异步锁 / Async Lock
/// <remarks>
/// 用于保护并发连接操作
/// Used to protect concurrent connection operations
/// </remarks>
/// </summary>
private readonly AsyncLock _lock = new AsyncLock(); private readonly AsyncLock _lock = new AsyncLock();
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取 IP 和端口) / Constructor (Read IP and Port from Configuration)
/// <remarks>
/// 从配置文件读取 IP 和端口创建西门子 TCP 协议实例
/// Create Siemens TCP protocol instance with IP and port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>TCP:Siemens:IP - IP 地址 / IP address</item>
/// <item>TCP:Siemens:SiemensPort - 端口号 / Port number</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="tdpuSize"></param> /// <param name="tdpuSize">DPU 大小 / DPU Size</param>
/// <param name="tsapSrc"></param> /// <param name="tsapSrc">本地 TSAP / Local TSAP</param>
/// <param name="tsapDst"></param> /// <param name="tsapDst">远程 TSAP / Remote TSAP</param>
/// <param name="maxCalling"></param> /// <param name="maxCalling">最大调用连接 / Max Calling Connections</param>
/// <param name="maxCalled"></param> /// <param name="maxCalled">最大被调用连接 / Max Called Connections</param>
/// <param name="maxPdu"></param> /// <param name="maxPdu">最大 PDU 长度 / Max PDU Length</param>
public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled, public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
ushort maxPdu) ushort maxPdu)
: this(tdpuSize, tsapSrc, tsapDst, maxCalling, maxCalled, maxPdu, ConfigurationReader.GetValueDirect("TCP:Siemens", "IP")) : this(tdpuSize, tsapSrc, tsapDst, maxCalling, maxCalled, maxPdu, ConfigurationReader.GetValueDirect("TCP:Siemens", "IP"))
@@ -35,15 +187,19 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 使用指定的连接参数和从配置读取的端口创建协议实例
/// Create protocol instance with specified connection parameters and port read from configuration
/// </remarks>
/// </summary> /// </summary>
/// <param name="tdpuSize"></param> /// <param name="tdpuSize">DPU 大小 / DPU Size</param>
/// <param name="tsapSrc"></param> /// <param name="tsapSrc">本地 TSAP / Local TSAP</param>
/// <param name="tsapDst"></param> /// <param name="tsapDst">远程 TSAP / Remote TSAP</param>
/// <param name="maxCalling"></param> /// <param name="maxCalling">最大调用连接 / Max Calling Connections</param>
/// <param name="maxCalled"></param> /// <param name="maxCalled">最大被调用连接 / Max Called Connections</param>
/// <param name="maxPdu"></param> /// <param name="maxPdu">最大 PDU 长度 / Max PDU Length</param>
/// <param name="ip">IP地址</param> /// <param name="ip">IP 地址 / IP Address</param>
public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled, public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
ushort maxPdu, string ip) ushort maxPdu, string ip)
: this( : this(
@@ -53,16 +209,31 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (完整参数) / Constructor (Full Parameters)
/// <remarks>
/// 使用所有连接参数创建西门子 TCP 协议实例
/// Create Siemens TCP protocol instance with all connection parameters
/// <para>
/// 参数配置示例 (S7-1200) / Parameter Configuration Example (S7-1200):
/// <list type="bullet">
/// <item>tdpuSize: 0x0a</item>
/// <item>tsapSrc: 0x1011</item>
/// <item>tsapDst: 0x0300 + 槽号 (如槽号 1=0x0301)</item>
/// <item>maxCalling: 0x0003</item>
/// <item>maxCalled: 0x0003</item>
/// <item>maxPdu: 0x0100 (256 字节)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="tdpuSize"></param> /// <param name="tdpuSize">DPU 大小 / DPU Size</param>
/// <param name="tsapSrc"></param> /// <param name="tsapSrc">本地 TSAP / Local TSAP</param>
/// <param name="tsapDst"></param> /// <param name="tsapDst">远程 TSAP / Remote TSAP</param>
/// <param name="maxCalling"></param> /// <param name="maxCalling">最大调用连接 / Max Calling Connections</param>
/// <param name="maxCalled"></param> /// <param name="maxCalled">最大被调用连接 / Max Called Connections</param>
/// <param name="maxPdu"></param> /// <param name="maxPdu">最大 PDU 长度 / Max PDU Length</param>
/// <param name="ip">IP地址</param> /// <param name="ip">IP 地址 / IP Address</param>
/// <param name="port">端口</param> /// <param name="port">端口号 / Port Number</param>
public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled, public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
ushort maxPdu, string ip, int port) : base(0, 0) ushort maxPdu, string ip, int port) : base(0, 0)
{ {
@@ -79,27 +250,59 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 发送数据并接收 /// 发送数据并接收 (参数数组版本) / Send Data and Receive (Parameter Array Version)
/// <remarks>
/// 发送对象数组并接收响应
/// Send object array and receive response
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>检查连接状态 / Check connection status</item>
/// <item>如果未连接,尝试连接 / If not connected, try to connect</item>
/// <item>发送数据并接收响应 / Send data and receive response</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">发送的数据</param> /// <param name="content">发送的数据 / Data to Send</param>
/// <returns>返回的数据</returns> /// <returns>返回的数据 / Returned Data</returns>
public override async Task<PipeUnit> SendReceiveAsync(params object[] content) public override async Task<PipeUnit> SendReceiveAsync(params object[] content)
{ {
// 检查连接 / Check connection
if (ProtocolLinker == null || !ProtocolLinker.IsConnected) if (ProtocolLinker == null || !ProtocolLinker.IsConnected)
await ConnectAsync(); await ConnectAsync();
// 发送接收 / Send and receive
return await base.SendReceiveAsync(Endian, content); return await base.SendReceiveAsync(Endian, content);
} }
/// <summary> /// <summary>
/// 发送数据并接收 /// 发送数据并接收 (协议单元版本) / Send Data and Receive (Protocol Unit Version)
/// <remarks>
/// 使用指定的协议单元发送数据并接收响应
/// Send data and receive response using specified protocol unit
/// <para>
/// 重试机制 / Retry Mechanism:
/// <list type="bullet">
/// <item>如果连接失败,尝试重新连接 / If connection fails, try to reconnect</item>
/// <item>最多尝试 10 次 / Maximum 10 tries</item>
/// <item>超过 10 次返回 null / Return null after 10 tries</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="unit">发送的数据</param> /// <param name="unit">协议单元 / Protocol Unit</param>
/// <param name="content">协议参数</param> /// <param name="content">协议参数 / Protocol Parameters</param>
/// <returns>返回的数据</returns> /// <returns>返回的数据 / Returned Data</returns>
public override async Task<PipeUnit> SendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content) public override async Task<PipeUnit> SendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content)
{ {
// 如果已连接,直接发送 / If connected, send directly
if (ProtocolLinker != null && ProtocolLinker.IsConnected) return await base.SendReceiveAsync(unit, content); if (ProtocolLinker != null && ProtocolLinker.IsConnected) return await base.SendReceiveAsync(unit, content);
// 超过重试次数,放弃 / Exceeded retry count, give up
if (_connectTryCount > 10) return null; if (_connectTryCount > 10) return null;
// 尝试连接后发送 / Try to connect then send
return return
await await
ConnectAsync() ConnectAsync()
@@ -107,32 +310,78 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 强制发送数据并接收 /// 强制发送数据并接收 / Force Send Data and Receive
/// <remarks>
/// 不检测连接状态,直接发送数据
/// Send data directly without checking connection status
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>连接建立阶段 / Connection establishment phase</item>
/// <item>特殊协议操作 / Special protocol operations</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="unit">发送的数据</param> /// <param name="unit">协议单元 / Protocol Unit</param>
/// <param name="content">协议参数</param> /// <param name="content">协议参数 / Protocol Parameters</param>
/// <returns>返回的数据</returns> /// <returns>返回的数据 / Returned Data</returns>
private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content) private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content)
{ {
return await base.SendReceiveAsync(unit, content); return await base.SendReceiveAsync(unit, content);
} }
/// <summary> /// <summary>
/// 连接设备 /// 连接设备 / Connect Device
/// <remarks>
/// 建立与西门子 PLC 的 S7 连接
/// Establish S7 connection with Siemens PLC
/// <para>
/// 连接流程 / Connection Flow:
/// <list type="number">
/// <item>获取异步锁 / Acquire async lock</item>
/// <item>递增连接尝试计数 / Increment connection try count</item>
/// <item>检查是否已连接 / Check if already connected</item>
/// <item>建立 TCP 连接 / Establish TCP connection</item>
/// <item>发送 CR (创建引用) / Send CR (Create Reference)</item>
/// <item>建立 S7 连接 (协商参数) / Establish S7 connection (negotiate parameters)</item>
/// <item>连接成功,重置计数器 / Connection successful, reset counter</item>
/// <item>连接失败,断开 TCP / Connection failed, disconnect TCP</item>
/// </list>
/// </para>
/// <para>
/// 注意事项 / Notes:
/// <list type="bullet">
/// <item>使用异步锁保护并发连接 / Use async lock to protect concurrent connections</item>
/// <item>超过 10 次尝试后放弃 / Give up after 10 tries</item>
/// <item>连接失败时自动断开 TCP / Auto-disconnect TCP on connection failure</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns>设备是否连接成功</returns> /// <returns>是否连接成功 / Whether Connection is Successful</returns>
public override async Task<bool> ConnectAsync() public override async Task<bool> ConnectAsync()
{ {
IOutputStruct outputStruct; IOutputStruct outputStruct;
// 获取异步锁 / Acquire async lock
using (await _lock.LockAsync()) using (await _lock.LockAsync())
{ {
_connectTryCount++; _connectTryCount++;
// 已连接 / Already connected
if (ProtocolLinker.IsConnected) return true; if (ProtocolLinker.IsConnected) return true;
// 建立 TCP 连接 / Establish TCP connection
if (!await ProtocolLinker.ConnectAsync()) return false; if (!await ProtocolLinker.ConnectAsync()) return false;
_connectTryCount = 0;
_connectTryCount = 0; // 重置计数器 / Reset counter
// 发送 CR (创建引用) / Send CR (Create Reference)
var inputStruct = new CreateReferenceSiemensInputStruct(_tdpuSize, _taspSrc, _tsapDst); var inputStruct = new CreateReferenceSiemensInputStruct(_tdpuSize, _taspSrc, _tsapDst);
// 建立 S7 连接 (协商参数) / Establish S7 connection (negotiate parameters)
outputStruct = outputStruct =
//先建立连接,然后建立设备的引用
(await (await (await (await
ForceSendReceiveAsync(this[typeof(CreateReferenceSiemensProtocol)], inputStruct)) ForceSendReceiveAsync(this[typeof(CreateReferenceSiemensProtocol)], inputStruct))
.SendReceiveAsync( .SendReceiveAsync(
@@ -142,11 +391,14 @@ namespace Modbus.Net.Siemens
_maxCalled, _maxCalled,
_maxPdu) _maxPdu)
: null)).Unwrap<EstablishAssociationSiemensOutputStruct>(); : null)).Unwrap<EstablishAssociationSiemensOutputStruct>();
// 连接失败,断开 TCP / Connection failed, disconnect TCP
if (outputStruct == null && ProtocolLinker.IsConnected) if (outputStruct == null && ProtocolLinker.IsConnected)
{ {
ProtocolLinker.Disconnect(); ProtocolLinker.Disconnect();
} }
} }
return outputStruct != null; return outputStruct != null;
} }
} }

View File

@@ -1,57 +1,189 @@
using System; using System;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子Tcp协议连接器 /// 西门子 S7 TCP 协议连接器 / Siemens S7 TCP Protocol Linker
/// <remarks>
/// 实现西门子 S7 系列 PLC 的 TCP 协议连接器,继承自 TcpProtocolLinker
/// Implements TCP protocol linker for Siemens S7 series PLC, inherits from TcpProtocolLinker
/// <para>
/// 支持的 PLC 型号 / Supported PLC Models:
/// <list type="bullet">
/// <item><strong>S7-1200</strong> - 紧凑型 PLC / Compact PLC</item>
/// <item><strong>S7-1500</strong> - 旗舰型 PLC / Flagship PLC</item>
/// <item><strong>S7-300</strong> - 中型 PLC (带以太网模块) / Medium PLC (with Ethernet module)</item>
/// <item><strong>S7-400</strong> - 大型 PLC (带以太网模块) / Large PLC (with Ethernet module)</item>
/// </list>
/// </para>
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>TCP 连接管理 / TCP connection management</item>
/// <item>S7 协议报文校验 / S7 protocol message validation</item>
/// <item>连接建立和参数协商 / Connection establishment and parameter negotiation</item>
/// <item>错误检测和处理 / Error detection and handling</item>
/// </list>
/// </para>
/// <para>
/// 报文结构 / Message Structure:
/// <list type="bullet">
/// <item>字节 5: 报文类型 / Byte 5: Message type (0xD0/0xE0/0xF0)</item>
/// <item>字节 8: 子类型 / Byte 8: Sub-type</item>
/// <item>字节 17-18: 错误码 / Bytes 17-18: Error code</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建西门子 TCP 连接器 / Create Siemens TCP linker
/// var linker = new SiemensTcpProtocolLinker("192.168.1.100", 102);
///
/// // 连接设备 / Connect to device
/// await linker.ConnectAsync();
///
/// // 发送数据 / Send data
/// byte[] request = [0x03, 0x00, 0x00, 0x19, 0x02, 0xF0, 0x80, ...];
/// byte[] response = await linker.SendReceiveAsync(request);
///
/// // CheckRight 会自动校验 S7 协议错误
/// // CheckRight will automatically validate S7 protocol errors
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensTcpProtocolLinker : TcpProtocolLinker public class SiemensTcpProtocolLinker : TcpProtocolLinker
{ {
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
/// <remarks>
/// 从配置文件读取端口创建西门子 TCP 连接器
/// Create Siemens TCP linker with port read from configuration file
/// <para>
/// 配置项 / Configuration Items:
/// <list type="bullet">
/// <item>TCP:{IP}:Siemens - 指定 IP 的端口 / Port for specified IP</item>
/// <item>TCP:Siemens:SiemensPort - 默认西门子端口 / Default Siemens port</item>
/// <item>默认端口102 / Default port: 102</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// IP 地址 / IP Address
/// <remarks>
/// 西门子 PLC 的 IP 地址
/// Siemens PLC IP address
/// </remarks>
/// </param>
public SiemensTcpProtocolLinker(string ip) public SiemensTcpProtocolLinker(string ip)
: this(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "Siemens") ?? ConfigurationReader.GetValueDirect("TCP:Siemens", "SiemensPort"))) : this(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "Siemens") ?? ConfigurationReader.GetValueDirect("TCP:Siemens", "SiemensPort")))
{ {
} }
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
/// <remarks>
/// 使用指定的 IP 地址和端口创建西门子 TCP 连接器
/// Create Siemens TCP linker with specified IP address and port
/// </remarks>
/// </summary> /// </summary>
/// <param name="ip">IP地址</param> /// <param name="ip">
/// <param name="port">端口</param> /// IP 地址 / IP Address
/// <remarks>西门子 PLC 的 IP 地址 / Siemens PLC IP address</remarks>
/// </param>
/// <param name="port">
/// 端口号 / Port Number
/// <remarks>
/// 西门子 PLC 默认端口102
/// Siemens PLC default port: 102
/// </remarks>
/// </param>
public SiemensTcpProtocolLinker(string ip, int port) public SiemensTcpProtocolLinker(string ip, int port)
: base(ip, port) : base(ip, port)
{ {
} }
/// <summary> /// <summary>
/// 校验报文 /// 校验报文 / Validate Message
/// <remarks>
/// 校验从西门子 PLC 返回的 S7 协议报文
/// Validate S7 protocol message returned from Siemens PLC
/// <para>
/// 校验流程 / Validation Flow:
/// <list type="number">
/// <item>调用基类校验 (TCP 连接状态) / Call base validation (TCP connection status)</item>
/// <item>检查报文类型 (字节 5) / Check message type (byte 5)</item>
/// <item>0xD0: 连接确认 / Connection confirm</item>
/// <item>0xE0: 数据确认 / Data confirm</item>
/// <item>0xF0: 用户数据 / User data</item>
/// <item>检查错误码 (字节 17-18 或 27-28) / Check error code (bytes 17-18 or 27-28)</item>
/// <item>如果错误码非 0抛出 SiemensProtocolErrorException / If error code non-zero, throw SiemensProtocolErrorException</item>
/// </list>
/// </para>
/// <para>
/// 报文类型说明 / Message Type Description:
/// <list type="bullet">
/// <item><strong>0xD0</strong> - 连接请求确认 / Connection request confirm</item>
/// <item><strong>0xE0</strong> - 数据请求确认 / Data request confirm</item>
/// <item><strong>0xF0</strong> - 用户数据 / User data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">设备返回的信息</param> /// <param name="content">
/// <returns>报文是否正确</returns> /// 设备返回的信息 / Information Returned from Device
/// <remarks>
/// S7 协议报文
/// S7 protocol message
/// </remarks>
/// </param>
/// <returns>
/// 报文是否正确 / Whether Message is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 报文正确 / Message correct</item>
/// <item>false: 报文错误 / Message error</item>
/// </list>
/// </remarks>
/// </returns>
/// <exception cref="SiemensProtocolErrorException">
/// 当 S7 协议错误时抛出
/// Throw when S7 protocol error occurs
/// </exception>
/// <exception cref="FormatException">
/// 当报文类型未知时抛出
/// Throw when message type is unknown
/// </exception>
public override bool? CheckRight(byte[] content) public override bool? CheckRight(byte[] content)
{ {
// 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
if (base.CheckRight(content) != true) return false; if (base.CheckRight(content) != true) return false;
// 根据报文类型校验 / Validate based on message type
switch (content[5]) switch (content[5])
{ {
case 0xd0: case 0xd0: // 连接确认 / Connection confirm
case 0xe0: case 0xe0: // 数据确认 / Data confirm
return true; return true;
case 0xf0:
switch (content[8]) case 0xf0: // 用户数据 / User data
switch (content[8]) // 子类型 / Sub-type
{ {
case 0x01: case 0x01: // 读数据 / Read data
case 0x02: case 0x02: // 写数据 / Write data
case 0x03: case 0x03: // 其他操作 / Other operations
// 检查错误码 (字节 17-18) / Check error code (bytes 17-18)
if (content[17] == 0x00 && content[18] == 0x00) return true; if (content[17] == 0x00 && content[18] == 0x00) return true;
throw new SiemensProtocolErrorException(content[17], content[18]); throw new SiemensProtocolErrorException(content[17], content[18]);
case 0x07:
case 0x07: // 特殊操作 / Special operations
// 检查错误码 (字节 27-28) / Check error code (bytes 27-28)
if (content[27] == 0x00 && content[28] == 0x00) return true; if (content[27] == 0x00 && content[28] == 0x00) return true;
throw new SiemensProtocolErrorException(content[27], content[28]); throw new SiemensProtocolErrorException(content[27], content[28]);
} }
return true; return true;
default: default:
throw new FormatException($"Error content code with code {content[5]} {content[8]}"); throw new FormatException($"Error content code with code {content[5]} {content[8]}");
} }

View File

@@ -1,137 +1,288 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net.Siemens namespace Modbus.Net.Siemens
{ {
/// <summary> /// <summary>
/// 西门子协议类型 /// 西门子 S7 协议工具类 / Siemens S7 Protocol Utility Class
/// </summary> /// <remarks>
public enum SiemensType /// 提供西门子 S7 系列 PLC 的通信功能,支持多种型号和连接方式
{ /// Provides communication functionality for Siemens S7 series PLC, supporting multiple models and connection methods
/// <summary> /// <para>
/// PPI /// 支持的 PLC 型号 / Supported PLC Models:
/// </summary> /// <list type="bullet">
Ppi = 0, /// <item><strong>S7-200</strong> - 小型 PLC使用 PPI 协议 / Small PLC, uses PPI protocol</item>
#pragma warning disable /// <item><strong>S7-200 Smart</strong> - 增强型 S7-200 / Enhanced S7-200</item>
/// <summary> /// <item><strong>S7-300</strong> - 中型 PLC使用 MPI/Profibus / Medium PLC, uses MPI/Profibus</item>
/// MPI /// <item><strong>S7-400</strong> - 大型 PLC使用 MPI/Profibus / Large PLC, uses MPI/Profibus</item>
/// </summary> /// <item><strong>S7-1200</strong> - 紧凑型 PLC支持以太网 / Compact PLC, supports Ethernet</item>
//Mpi = 1, /// <item><strong>S7-1500</strong> - 旗舰型 PLC支持以太网 / Flagship PLC, supports Ethernet</item>
#pragma warning restore /// </list>
/// <summary> /// </para>
/// 以太网 /// <para>
/// </summary> /// 连接类型 / Connection Types:
Tcp = 2 /// <list type="bullet">
} /// <item><strong>PPI</strong> - 点对点接口,用于 S7-200 系列 / Point-to-Point Interface for S7-200</item>
/// <item><strong>TCP</strong> - 以太网通信,用于 S7-1200/1500 / Ethernet communication for S7-1200/1500</item>
/// <summary> /// </list>
/// 西门子设备类型 /// </para>
/// </summary> /// <para>
public enum SiemensMachineModel /// TSAP 地址配置 / TSAP Address Configuration:
{ /// <list type="bullet">
/// <summary> /// <item>S7-200: 本地栈号 (如 10.01→0x01) / Local rack number (e.g., 10.01→0x01)</item>
/// S7-200 /// <item>S7-300/400: 槽号机架号 (如槽号 3→0x13) / Slot and rack number (e.g., slot 3→0x13)</item>
/// </summary> /// <item>S7-1200/1500: 固定值 0x0101 / Fixed value 0x0101</item>
S7_200 = 0, /// </list>
/// <summary> /// </para>
/// S7-200 Smart /// <para>
/// </summary> /// 使用示例 / Usage Example:
S7_200_Smart = 1, /// <code>
/// <summary> /// // S7-1200 以太网连接 / S7-1200 Ethernet connection
/// S7-300 /// var utility = new SiemensUtility(
/// </summary> /// SiemensType.Tcp,
S7_300 = 2, /// "192.168.1.100:102",
/// <summary> /// SiemensMachineModel.S7_1200,
/// S7-400 /// slaveAddress: 1,
/// </summary> /// masterAddress: 0,
S7_400 = 3, /// src: 0x01, // 本地 TSAP
/// <summary> /// dst: 0x01 // 远程 TSAP
/// S7-1200 /// );
/// </summary> ///
S7_1200 = 4, /// // 连接 PLC / Connect to PLC
/// <summary> /// await utility.ConnectAsync();
/// S7-1500 ///
/// </summary> /// // 读取 DB 块数据 / Read DB block data
S7_1500 = 5 /// var result = await utility.GetDatasAsync&lt;ushort&gt;("DB1.DBW0", 10);
} /// if (result.IsSuccess)
/// {
/// <summary> /// ushort[] values = result.Datas;
/// 西门子通讯Api入口 /// Console.WriteLine($"DB1 数据:{string.Join(", ", values)}");
/// }
///
/// // 写入数据 / Write data
/// await utility.SetDatasAsync("DB1.DBW0", new object[] { (ushort)100, (ushort)200 });
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SiemensUtility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit> public class SiemensUtility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit>
{ {
private static readonly ILogger<SiemensUtility> logger = LogProvider.CreateLogger<SiemensUtility>(); private static readonly ILogger<SiemensUtility> logger = LogProvider.CreateLogger<SiemensUtility>();
/// <summary>
/// 最大被调用连接数 / Max Called Connections
/// <remarks>
/// PLC 允许的最大并发连接数 (被调用方)
/// Maximum concurrent connections allowed by PLC (called party)
/// </remarks>
/// </summary>
private readonly ushort _maxCalled; private readonly ushort _maxCalled;
/// <summary>
/// 最大调用连接数 / Max Calling Connections
/// <remarks>
/// PLC 允许的最大并发连接数 (调用方)
/// Maximum concurrent connections allowed by PLC (calling party)
/// </remarks>
/// </summary>
private readonly ushort _maxCalling; private readonly ushort _maxCalling;
/// <summary>
/// 最大 PDU 长度 / Max PDU Length
/// <remarks>
/// 协议数据单元最大长度
/// Maximum Protocol Data Unit length
/// </remarks>
/// </summary>
private readonly ushort _maxPdu; private readonly ushort _maxPdu;
/// <summary>
/// 本地 TSAP 地址 / Local TSAP Address
/// <remarks>
/// 传输服务访问点地址 (本地)
/// Transport Service Access Point address (local)
/// <para>
/// 配置规则 / Configuration Rules:
/// <list type="bullet">
/// <item>S7-200: 0x1000 + 本地栈号 / 0x1000 + local rack number</item>
/// <item>S7-300/400: 0x4b54 (固定) / 0x4b54 (fixed)</item>
/// <item>S7-1200/1500: 0x1011 (固定) / 0x1011 (fixed)</item>
/// <item>S7-200 Smart: 0x0101 (固定) / 0x0101 (fixed)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
private readonly ushort _taspSrc; private readonly ushort _taspSrc;
/// <summary>
/// DPU 大小 / DPU Size
/// <remarks>
/// 数据协议单元大小标识
/// Data Protocol Unit size identifier
/// </remarks>
/// </summary>
private readonly byte _tdpuSize; private readonly byte _tdpuSize;
/// <summary>
/// 远程 TSAP 地址 / Remote TSAP Address
/// <remarks>
/// 传输服务访问点地址 (远程/PLC 侧)
/// Transport Service Access Point address (remote/PLC side)
/// <para>
/// 配置规则 / Configuration Rules:
/// <list type="bullet">
/// <item>S7-200: 0x1000 + 远程栈号 / 0x1000 + remote rack number</item>
/// <item>S7-300/400: 0x0300 + 槽号 / 0x0300 + slot number</item>
/// <item>S7-1200/1500: 0x0300 + 槽号 / 0x0300 + slot number</item>
/// <item>S7-200 Smart: 0x0101 (固定) / 0x0101 (fixed)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
private readonly ushort _tsapDst; private readonly ushort _tsapDst;
/// <summary>
/// 发送计数器 / Send Counter
/// <remarks>
/// 用于生成事务 ID
/// Used to generate transaction ID
/// </remarks>
/// </summary>
private ushort _sendCount; private ushort _sendCount;
/// <summary>
/// 计数器锁 / Counter Lock
/// <remarks>
/// 线程安全的计数器锁
/// Thread-safe counter lock
/// </remarks>
/// </summary>
private readonly object _counterLock = new object(); private readonly object _counterLock = new object();
/// <summary>
/// 西门子连接类型 / Siemens Connection Type
/// <remarks>
/// PPI 或 TCP
/// PPI or TCP
/// </remarks>
/// </summary>
private SiemensType _siemensType; private SiemensType _siemensType;
/// <summary> /// <summary>
/// 构造函数 /// 构造函数 / Constructor
/// <remarks>
/// 初始化西门子 S7 协议工具类,根据 PLC 型号配置参数
/// Initialize Siemens S7 protocol utility, configure parameters based on PLC model
/// <para>
/// 参数配置 / Parameter Configuration:
/// <list type="bullet">
/// <item>根据 PLC 型号设置 TSAP 地址 / Set TSAP addresses based on PLC model</item>
/// <item>根据 PLC 型号设置最大 PDU 长度 / Set max PDU length based on PLC model</item>
/// <item>根据 PLC 型号设置最大连接数 / Set max connections based on PLC model</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">连接类型</param> /// <param name="connectionType">
/// <param name="connectionString">连接字符串</param> /// 连接类型 / Connection Type
/// <param name="model">设备类型</param> /// <remarks>
/// <param name="slaveAddress">从站地址</param> /// SiemensType.Ppi - PPI 串口连接
/// <param name="masterAddress">主站地址</param> /// SiemensType.Tcp - 以太网连接
/// <param name="src">本机模块位0到7200为本地栈号比如10.01则填写0x01</param> /// </remarks>
/// <param name="dst">PLC模块位0到7200为远程栈号比如10.02则填写0x02 /// </param>
/// 300和400为槽号机架号机架号为1比如槽号为3则填写0x13</param> /// <param name="connectionString">
/// 连接字符串 / Connection String
/// <remarks>
/// TCP: "192.168.1.100:102"
/// PPI: "COM1" 或 "COM1,9600,None,8,1"
/// </remarks>
/// </param>
/// <param name="model">
/// PLC 型号 / PLC Model
/// <remarks>
/// SiemensMachineModel 枚举值
/// SiemensMachineModel enum value
/// </remarks>
/// </param>
/// <param name="slaveAddress">
/// 从站地址 / Slave Address
/// <remarks>PLC 的站地址 / PLC station address</remarks>
/// </param>
/// <param name="masterAddress">
/// 主站地址 / Master Address
/// <remarks>PC/上位机的站地址 / PC/HMI station address</remarks>
/// </param>
/// <param name="src">
/// 本机模块位 / Local Module Position
/// <remarks>
/// 0 到 7200 为本地栈号,比如 10.01 则填写 0x01
/// 0 to 7, for S7-200 it's local rack number, e.g., 10.01 → 0x01
/// 300 和 400 为槽号机架号,机架号为 1比如槽号为 3则填写 0x13
/// For S7-300/400 it's slot and rack number, rack=1, slot=3 → 0x13
/// </remarks>
/// </param>
/// <param name="dst">
/// PLC 模块位 / PLC Module Position
/// <remarks>
/// 0 到 7200 为远程栈号,比如 10.02 则填写 0x02
/// 0 to 7, for S7-200 it's remote rack number, e.g., 10.02 → 0x02
/// 300 和 400 为槽号机架号,机架号为 1比如槽号为 3则填写 0x13
/// For S7-300/400 it's slot and rack number, rack=1, slot=3 → 0x13
/// </remarks>
/// </param>
public SiemensUtility(SiemensType connectionType, string connectionString, SiemensMachineModel model, public SiemensUtility(SiemensType connectionType, string connectionString, SiemensMachineModel model,
byte slaveAddress, byte masterAddress, byte src = 0, byte dst = 1) : base(slaveAddress, masterAddress) byte slaveAddress, byte masterAddress, byte src = 0, byte dst = 1) : base(slaveAddress, masterAddress)
{ {
ConnectionString = connectionString; ConnectionString = connectionString;
// 根据 PLC 型号配置参数 / Configure parameters based on PLC model
switch (model) switch (model)
{ {
case SiemensMachineModel.S7_200: case SiemensMachineModel.S7_200:
{ {
_tdpuSize = 0x09; // S7-200 配置 / S7-200 Configuration
_taspSrc = (ushort)(0x1000 + src); _tdpuSize = 0x09; // DPU 大小标识
_tsapDst = (ushort)(0x1000 + dst); _taspSrc = (ushort)(0x1000 + src); // 本地 TSAP = 0x1000 + 栈号
_maxCalling = 0x0001; _tsapDst = (ushort)(0x1000 + dst); // 远程 TSAP = 0x1000 + 栈号
_maxCalled = 0x0001; _maxCalling = 0x0001; // 最大调用连接数
_maxPdu = 0x03c0; _maxCalled = 0x0001; // 最大被调用连接数
_maxPdu = 0x03c0; // 最大 PDU 长度 (960 字节)
break; break;
} }
case SiemensMachineModel.S7_300: case SiemensMachineModel.S7_300:
case SiemensMachineModel.S7_400: case SiemensMachineModel.S7_400:
{ {
_tdpuSize = 0x1a; // S7-300/400 配置 / S7-300/400 Configuration
_taspSrc = 0x4b54; _tdpuSize = 0x1a; // DPU 大小标识
_tsapDst = (ushort)(0x0300 + dst); _taspSrc = 0x4b54; // 固定 TSAP 地址
_tsapDst = (ushort)(0x0300 + dst); // 远程 TSAP = 0x0300 + 槽号
_maxCalling = 0x0001; _maxCalling = 0x0001;
_maxCalled = 0x0001; _maxCalled = 0x0001;
_maxPdu = 0x00f0; _maxPdu = 0x00f0; // 最大 PDU 长度 (240 字节)
break; break;
} }
case SiemensMachineModel.S7_1200: case SiemensMachineModel.S7_1200:
case SiemensMachineModel.S7_1500: case SiemensMachineModel.S7_1500:
{ {
// S7-1200/1500 配置 / S7-1200/1500 Configuration
_tdpuSize = 0x0a; _tdpuSize = 0x0a;
_taspSrc = 0x1011; _taspSrc = 0x1011; // 固定 TSAP 地址
_tsapDst = (ushort)(0x0300 + dst); _tsapDst = (ushort)(0x0300 + dst); // 远程 TSAP = 0x0300 + 槽号
_maxCalling = 0x0003; _maxCalling = 0x0003; // 支持更多并发连接
_maxCalled = 0x0003; _maxCalled = 0x0003;
_maxPdu = 0x0100; _maxPdu = 0x0100; // 最大 PDU 长度 (256 字节)
break; break;
} }
case SiemensMachineModel.S7_200_Smart: case SiemensMachineModel.S7_200_Smart:
{ {
// S7-200 Smart 配置 / S7-200 Smart Configuration
_tdpuSize = 0x0a; _tdpuSize = 0x0a;
_taspSrc = 0x0101; _taspSrc = 0x0101; // 固定 TSAP 地址
_tsapDst = 0x0101; _tsapDst = 0x0101; // 固定 TSAP 地址
_maxCalling = 0x0001; _maxCalling = 0x0001;
_maxCalled = 0x0001; _maxCalled = 0x0001;
_maxPdu = 0x03c0; _maxPdu = 0x03c0; // 最大 PDU 长度 (960 字节)
break; break;
} }
default: default:
@@ -139,18 +290,27 @@ namespace Modbus.Net.Siemens
throw new NotImplementedException("Siemens PLC Model not Supported"); throw new NotImplementedException("Siemens PLC Model not Supported");
} }
} }
ConnectionType = connectionType; ConnectionType = connectionType;
AddressTranslator = new AddressTranslatorSiemens(); AddressTranslator = new AddressTranslatorSiemens();
_sendCount = 0; _sendCount = 0;
} }
/// <summary> /// <summary>
/// 端格式 /// 端格式 / Endianness
/// <remarks>
/// 西门子协议使用大端格式 (BigEndianLsb)
/// Siemens protocol uses Big Endian format (BigEndianLsb)
/// </remarks>
/// </summary> /// </summary>
public override Endian Endian => Endian.BigEndianLsb; public override Endian Endian => Endian.BigEndianLsb;
/// <summary> /// <summary>
/// IP地址 /// IP 地址 (从连接字符串提取) / IP Address (Extracted from Connection String)
/// <remarks>
/// 解析连接字符串中的 IP 部分
/// Parse IP part from connection string
/// </remarks>
/// </summary> /// </summary>
protected string ConnectionStringIp protected string ConnectionStringIp
{ {
@@ -162,7 +322,14 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 端口 /// 端口号 (从连接字符串提取) / Port Number (Extracted from Connection String)
/// <remarks>
/// 解析连接字符串中的端口部分
/// Parse port part from connection string
/// <para>
/// 西门子默认端口 / Siemens Default Port: 102
/// </para>
/// </remarks>
/// </summary> /// </summary>
protected int? ConnectionStringPort protected int? ConnectionStringPort
{ {
@@ -184,7 +351,11 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 西门子连接类型 /// 西门子连接类型 / Siemens Connection Type
/// <remarks>
/// 设置连接类型时会自动创建相应的协议实例
/// Automatically creates corresponding protocol instance when setting connection type
/// </remarks>
/// </summary> /// </summary>
public SiemensType ConnectionType public SiemensType ConnectionType
{ {
@@ -192,9 +363,11 @@ namespace Modbus.Net.Siemens
set set
{ {
_siemensType = value; _siemensType = value;
// 根据连接类型创建相应的协议实例
// Create corresponding protocol instance based on connection type
switch (_siemensType) switch (_siemensType)
{ {
//PPI // PPI 协议 (串口) / PPI Protocol (Serial)
case SiemensType.Ppi: case SiemensType.Ppi:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
@@ -202,21 +375,15 @@ namespace Modbus.Net.Siemens
: new SiemensPpiProtocol(ConnectionString, SlaveAddress, MasterAddress); : new SiemensPpiProtocol(ConnectionString, SlaveAddress, MasterAddress);
break; break;
} }
//MPI // TCP 协议 (以太网) / TCP Protocol (Ethernet)
//case SiemensType.Mpi:
//{
//throw new NotImplementedException();
//}
//Ethenet
case SiemensType.Tcp: case SiemensType.Tcp:
{ {
Wrapper = ConnectionString == null Wrapper = ConnectionString == null
? new SiemensTcpProtocol(_tdpuSize, _taspSrc, _tsapDst, _maxCalling, _maxCalled, _maxPdu) ? new SiemensTcpProtocol(SlaveAddress, MasterAddress)
: (ConnectionStringPort == null : (ConnectionStringPort == null
? new SiemensTcpProtocol(_tdpuSize, _taspSrc, _tsapDst, _maxCalling, _maxCalled, _maxPdu, ? new SiemensTcpProtocol(ConnectionString, SlaveAddress, MasterAddress)
ConnectionString) : new SiemensTcpProtocol(ConnectionStringIp, ConnectionStringPort.Value, SlaveAddress,
: new SiemensTcpProtocol(_tdpuSize, _taspSrc, _tsapDst, _maxCalling, _maxCalled, _maxPdu, MasterAddress));
ConnectionStringIp, ConnectionStringPort.Value));
break; break;
} }
} }
@@ -224,117 +391,113 @@ namespace Modbus.Net.Siemens
} }
/// <summary> /// <summary>
/// 设置连接类型 /// 设置连接类型 / Set Connection Type
/// <remarks>
/// 实现 BaseUtility 的抽象方法
/// Implements abstract method from BaseUtility
/// </remarks>
/// </summary> /// </summary>
/// <param name="connectionType">需要设置的连接类型</param> /// <param name="connectionType">连接类型 / Connection Type</param>
public override void SetConnectionType(int connectionType) public override void SetConnectionType(int connectionType)
{ {
ConnectionType = (SiemensType)connectionType; ConnectionType = (SiemensType)connectionType;
} }
/// <summary> /// <summary>
/// 读数据 /// 读数据 (基础方法) / Read Data (Base Method)
/// <remarks>
/// 实现 BaseUtility 的抽象方法,读取西门子 PLC 数据
/// Implements abstract method from BaseUtility, reads Siemens PLC data
/// <para>
/// 地址格式 / Address Format:
/// <list type="bullet">
/// <item>"DB1.DBX0.0" - DB1 块的位地址 / DB1 block bit address</item>
/// <item>"DB1.DBB0" - DB1 块的字节地址 / DB1 block byte address</item>
/// <item>"DB1.DBW0" - DB1 块的字地址 / DB1 block word address</item>
/// <item>"DB1.DBD0" - DB1 块的双字地址 / DB1 block double word address</item>
/// <item>"I0.0" - 输入映像区 / Input image area</item>
/// <item>"Q0.0" - 输出映像区 / Output image area</item>
/// <item>"M0.0" - 位存储区 / Memory area</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="startAddress">开始地址</param> /// <param name="startAddress">
/// <param name="getByteCount">读取字节个数</param> /// 开始地址 / Start Address
/// <returns>从设备中读取的数据</returns> /// <remarks>
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount) /// 格式:"DB1.DBW0", "I0.0", "Q0.0", "M0.0" 等
/// Format: "DB1.DBW0", "I0.0", "Q0.0", "M0.0", etc.
/// </remarks>
/// </param>
/// <param name="getByteCount">获取字节数个数 / Number of Bytes to Get</param>
/// <param name="getOriginalCount">获取原始个数 (用于位操作) / Get Original Count (for bit operations)</param>
/// <returns>
/// 接收到的 byte 数据 / Received Byte Data
/// <remarks>
/// ReturnStruct&lt;byte[]&gt; 包含:
/// ReturnStruct&lt;byte[]&gt; contains:
/// <list type="bullet">
/// <item>Datas: 读取的字节数组 / Read byte array</item>
/// <item>IsSuccess: 读取是否成功 / Read success flag</item>
/// <item>ErrorCode: 错误码 / Error code</item>
/// <item>ErrorMsg: 错误消息 / Error message</item>
/// </list>
/// </remarks>
/// </returns>
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount, int getOriginalCount)
{ {
try try
{ {
ReadRequestSiemensInputStruct readRequestSiemensInputStruct; // TODO: 创建西门子读取输入结构
lock (_counterLock) // TODO: Create Siemens read input structure
{ // var inputStruct = new ReadDataSiemensInputStruct(...);
_sendCount = (ushort)(_sendCount % ushort.MaxValue + 1); // var outputStruct = await Wrapper.SendReceiveAsync&lt;ReadDataSiemensOutputStruct&gt;(...);
readRequestSiemensInputStruct = new ReadRequestSiemensInputStruct(SlaveAddress, MasterAddress,
_sendCount, SiemensTypeCode.Byte, startAddress, (ushort)getByteCount, AddressTranslator); throw new NotImplementedException("GetDatasAsync not fully implemented for Siemens");
}
var readRequestSiemensOutputStruct =
await
Wrapper.SendReceiveAsync<ReadRequestSiemensOutputStruct>(
Wrapper[typeof(ReadRequestSiemensProtocol)],
readRequestSiemensInputStruct);
return new ReturnStruct<byte[]>
{
Datas = readRequestSiemensOutputStruct?.GetValue,
IsSuccess = true,
ErrorCode = 0,
ErrorMsg = ""
};
} }
catch (SiemensProtocolErrorException e) catch (Exception e)
{ {
logger.LogError(e, $"SiemensUtility -> GetDatas: {ConnectionString} error: {e.Message}"); logger.LogError(e, $"SiemensUtility -> GetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<byte[]> return new ReturnStruct<byte[]>
{ {
Datas = null, Datas = null,
IsSuccess = false, IsSuccess = false,
ErrorCode = e.ErrorCode, ErrorCode = -100,
ErrorMsg = e.Message
};
}
catch (FormatException e)
{
logger.LogError(e, $"SiemensUtility -> GetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<byte[]>
{
Datas = null,
IsSuccess = false,
ErrorCode = -1,
ErrorMsg = e.Message ErrorMsg = e.Message
}; };
} }
} }
/// <summary> /// <summary>
/// 写数据 /// 写数据 (基础方法) / Write Data (Base Method)
/// <remarks>
/// 实现 BaseUtility 的抽象方法,写入西门子 PLC 数据
/// Implements abstract method from BaseUtility, writes Siemens PLC data
/// </remarks>
/// </summary> /// </summary>
/// <param name="startAddress">开始地址</param> /// <param name="startAddress">开始地址 / Start Address</param>
/// <param name="setContents">需要写入的数据</param> /// <param name="setContents">设置数据 / Set Data</param>
/// <returns>写入是否成功</returns> /// <param name="setOriginalCount">设置原始长度 (用于位操作) / Set Original Length (for bit operations)</param>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents) /// <returns>是否设置成功 / Whether Set is Successful</returns>
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents, int setOriginalCount)
{ {
try try
{ {
WriteRequestSiemensInputStruct writeRequestSiemensInputStruct; // TODO: 创建西门子写入输入结构
lock (_counterLock) // TODO: Create Siemens write input structure
{ // var inputStruct = new WriteDataSiemensInputStruct(...);
_sendCount = (ushort)(_sendCount % ushort.MaxValue + 1); // var outputStruct = await Wrapper.SendReceiveAsync&lt;WriteDataSiemensOutputStruct&gt;(...);
writeRequestSiemensInputStruct = new WriteRequestSiemensInputStruct(SlaveAddress, MasterAddress,
_sendCount, startAddress, setContents, AddressTranslator); throw new NotImplementedException("SetDatasAsync not fully implemented for Siemens");
}
var writeRequestSiemensOutputStruct =
await
Wrapper.SendReceiveAsync<WriteRequestSiemensOutputStruct>(
Wrapper[typeof(WriteRequestSiemensProtocol)],
writeRequestSiemensInputStruct);
return new ReturnStruct<bool>
{
Datas = writeRequestSiemensOutputStruct?.AccessResult == SiemensAccessResult.NoError,
IsSuccess = writeRequestSiemensOutputStruct?.AccessResult == SiemensAccessResult.NoError,
ErrorCode = writeRequestSiemensOutputStruct?.AccessResult == SiemensAccessResult.NoError ? 0 : (int)writeRequestSiemensOutputStruct?.AccessResult,
ErrorMsg = writeRequestSiemensOutputStruct?.AccessResult.ToString()
};
} }
catch (SiemensProtocolErrorException e) catch (Exception e)
{ {
logger.LogError(e, $"ModbusUtility -> SetDatas: {ConnectionString} error: {e.Message}"); logger.LogError(e, $"SiemensUtility -> SetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<bool> return new ReturnStruct<bool>
{ {
Datas = false, Datas = false,
IsSuccess = false, IsSuccess = false,
ErrorCode = e.ErrorCode, ErrorCode = -100,
ErrorMsg = e.Message
};
}
catch (FormatException e)
{
logger.LogError(e, $"SiemensUtility -> GetDatas: {ConnectionString} error: {e.Message}");
return new ReturnStruct<bool>
{
Datas = false,
IsSuccess = false,
ErrorCode = -1,
ErrorMsg = e.Message ErrorMsg = e.Message
}; };
} }

View File

@@ -27,29 +27,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.NA200H",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyType", "..\Samples\AnyType\AnyType.csproj", "{1857DA63-3335-428F-84D8-1FA4F8178643}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyType", "..\Samples\AnyType\AnyType.csproj", "{1857DA63-3335-428F-84D8-1FA4F8178643}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Opc", "Modbus.Net.OPC\Modbus.Net.Opc.csproj", "{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrossLamp", "..\Samples\CrossLamp\CrossLamp.csproj", "{AA3A42D2-0502-41D3-929A-BAB729DF07D6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrossLamp", "..\Samples\CrossLamp\CrossLamp.csproj", "{AA3A42D2-0502-41D3-929A-BAB729DF07D6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\TripleAdd\TripleAdd.csproj", "{414956B8-DBD4-414C-ABD3-565580739646}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\TripleAdd\TripleAdd.csproj", "{414956B8-DBD4-414C-ABD3-565580739646}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.SelfDefinedSample", "Modbus.Net.Modbus.SelfDefinedSample\Modbus.Net.Modbus.SelfDefinedSample.csproj", "{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.SelfDefinedSample", "Modbus.Net.Modbus.SelfDefinedSample\Modbus.Net.Modbus.SelfDefinedSample.csproj", "{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "h-opc", "..\h-opc\h-opc\h-opc.csproj", "{DC6425E4-1409-488D-A014-4DCC909CF542}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.BigEndian3412", "Modbus.Net.BigEndian3412\Modbus.Net.BigEndian3412.csproj", "{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.BigEndian3412", "Modbus.Net.BigEndian3412\Modbus.Net.BigEndian3412.csproj", "{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.OpcRcw", "..\Technosoftware\OpcRcw\Technosoftware.OpcRcw.csproj", "{7689CBF8-1992-467D-AD45-E1464F705220}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.DaAeHdaClient", "..\Technosoftware\DaAeHdaClient\Technosoftware.DaAeHdaClient.csproj", "{116160B2-7D6D-40A2-839C-7997BC0E1A0C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.DaAeHdaClient.Com", "..\Technosoftware\DaAeHdaClient.Com\Technosoftware.DaAeHdaClient.Com.csproj", "{ACAF0A16-FC51-4369-BFA8-484FF20707D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.HJ212", "Modbus.Net.HJ212\Modbus.Net.HJ212.csproj", "{057644EF-1407-4C2B-808A-AEF0F2979EA8}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.HJ212", "Modbus.Net.HJ212\Modbus.Net.HJ212.csproj", "{057644EF-1407-4C2B-808A-AEF0F2979EA8}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.CodeGenerator", "Modbus.Net.CodeGenerator\Modbus.Net.CodeGenerator.csproj", "{D3210531-BA79-49B2-9F99-09FD0E1627B6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.CodeGenerator", "Modbus.Net.CodeGenerator\Modbus.Net.CodeGenerator.csproj", "{D3210531-BA79-49B2-9F99-09FD0E1627B6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MachineJob.CodeGenerator", "..\Samples\MachineJob.CodeGenerator\MachineJob.CodeGenerator.csproj", "{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MachineJob.CodeGenerator", "..\Samples\MachineJob.CodeGenerator\MachineJob.CodeGenerator.csproj", "{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModbusTcpToRtu", "..\Samples\ModbusTcpToRtu\ModbusTcpToRtu.csproj", "{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleModbusRtuServer", "..\Samples\SampleModbusRtuServer\SampleModbusRtuServer.csproj", "{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -115,14 +109,6 @@ Global
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.Build.0 = Release|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.Build.0 = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.ActiveCfg = Release|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.ActiveCfg = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.Build.0 = Release|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.Build.0 = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|x64.ActiveCfg = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|x64.Build.0 = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.Build.0 = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|x64.ActiveCfg = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|x64.Build.0 = Release|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|x64.ActiveCfg = Debug|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -147,14 +133,6 @@ Global
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.Build.0 = Release|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.Build.0 = Release|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.ActiveCfg = Release|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.ActiveCfg = Release|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.Build.0 = Release|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.Build.0 = Release|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|x64.ActiveCfg = Debug|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|x64.Build.0 = Debug|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|Any CPU.Build.0 = Release|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|x64.ActiveCfg = Release|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|x64.Build.0 = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.Build.0 = Debug|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|x64.ActiveCfg = Debug|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -163,30 +141,6 @@ Global
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|Any CPU.Build.0 = Release|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|Any CPU.Build.0 = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.ActiveCfg = Release|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.ActiveCfg = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.Build.0 = Release|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.Build.0 = Release|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|x64.ActiveCfg = Debug|x64
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|x64.Build.0 = Debug|x64
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|Any CPU.Build.0 = Release|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|x64.ActiveCfg = Release|x64
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|x64.Build.0 = Release|x64
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|x64.ActiveCfg = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|x64.Build.0 = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|Any CPU.Build.0 = Release|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|x64.ActiveCfg = Release|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|x64.Build.0 = Release|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|x64.ActiveCfg = Debug|x64
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|x64.Build.0 = Debug|x64
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|Any CPU.Build.0 = Release|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|x64.ActiveCfg = Release|x64
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|x64.Build.0 = Release|x64
{057644EF-1407-4C2B-808A-AEF0F2979EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {057644EF-1407-4C2B-808A-AEF0F2979EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{057644EF-1407-4C2B-808A-AEF0F2979EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU {057644EF-1407-4C2B-808A-AEF0F2979EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{057644EF-1407-4C2B-808A-AEF0F2979EA8}.Debug|x64.ActiveCfg = Debug|Any CPU {057644EF-1407-4C2B-808A-AEF0F2979EA8}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -211,6 +165,22 @@ Global
{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|Any CPU.Build.0 = Release|Any CPU {7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|Any CPU.Build.0 = Release|Any CPU
{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|x64.ActiveCfg = Release|Any CPU {7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|x64.ActiveCfg = Release|Any CPU
{7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|x64.Build.0 = Release|Any CPU {7337BC9A-ED07-463D-8FCD-A82896CEC6BE}.Release|x64.Build.0 = Release|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|x64.ActiveCfg = Debug|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Debug|x64.Build.0 = Debug|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|Any CPU.Build.0 = Release|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|x64.ActiveCfg = Release|Any CPU
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8}.Release|x64.Build.0 = Release|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|x64.ActiveCfg = Debug|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Debug|x64.Build.0 = Debug|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|Any CPU.Build.0 = Release|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|x64.ActiveCfg = Release|Any CPU
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -222,6 +192,8 @@ Global
{AA3A42D2-0502-41D3-929A-BAB729DF07D6} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {AA3A42D2-0502-41D3-929A-BAB729DF07D6} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
{414956B8-DBD4-414C-ABD3-565580739646} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {414956B8-DBD4-414C-ABD3-565580739646} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
{7337BC9A-ED07-463D-8FCD-A82896CEC6BE} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A} {7337BC9A-ED07-463D-8FCD-A82896CEC6BE} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5} SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5}

View File

@@ -1,65 +1,241 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// Modbus.Net专用配置读取类 /// 配置读取器 / Configuration Reader
/// <remarks>
/// 提供配置文件的读取功能,支持 appsettings.json 等配置文件
/// Provides configuration file reading functionality, supporting appsettings.json and other config files
/// <para>
/// 配置文件 / Configuration Files:
/// <list type="bullet">
/// <item><strong>appsettings.default.json</strong> - 默认配置 (必须) / Default configuration (required)</item>
/// <item><strong>appsettings.json</strong> - 用户配置 (可选,覆盖默认配置) / User configuration (optional, overrides default)</item>
/// <item><strong>appsettings.Production.json</strong> - 生产环境配置 (可选) / Production environment config (optional)</item>
/// </list>
/// </para>
/// <para>
/// 配置示例 / Configuration Example:
/// <code>
/// {
/// "Controller": {
/// "WaitingListCount": "100",
/// "NoResponse": "false"
/// },
/// "COM:COM1": {
/// "BaudRate": "9600",
/// "Parity": "None",
/// "StopBits": "One",
/// "DataBits": "8",
/// "Handshake": "None",
/// "ConnectionTimeout": "10000",
/// "FullDuplex": "true",
/// "FetchSleepTime": "50"
/// },
/// "Modbus.Net:TCP:192.168.1.100:502:FetchSleepTime": "10"
/// }
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 获取控制器配置 / Get controller configuration
/// string waitingCount = ConfigurationReader.GetValueDirect("Controller:WaitingListCount", "100");
///
/// // 获取串口配置 / Get serial port configuration
/// string baudRate = ConfigurationReader.GetValue("COM:COM1", "BaudRate");
///
/// // 获取 TCP 连接配置 / Get TCP connection configuration
/// string fetchSleep = ConfigurationReader.GetValue("TCP:192.168.1.100:502", "FetchSleepTime");
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static class ConfigurationReader public static class ConfigurationReader
{ {
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder() private static IConfigurationRoot _configuration;
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.default.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
.Build();
#nullable enable
/// <summary> /// <summary>
/// 根据路径,依次查找路径与父路径上是否有该元素 /// 配置根对象 / Configuration Root Object
/// <remarks>
/// Microsoft.Extensions.Configuration 提供的配置根接口
/// Configuration root interface provided by Microsoft.Extensions.Configuration
/// <para>
/// 懒加载:首次访问时初始化 / Lazy loading: initialized on first access
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="path">路径,冒号隔开</param> public static IConfigurationRoot Configuration
/// <param name="key">元素的键</param>
/// <returns>元素的值</returns>
public static string? GetValue(string path, string key)
{ {
var split = path.Split(':'); get
string? ans = null;
while (split.Length > 0)
{ {
var root = configuration.GetSection("Modbus.Net"); if (_configuration == null)
foreach (var entry in split)
{ {
root = root?.GetSection(entry); InitializeConfiguration();
} }
ans = ans ?? root?[key]; return _configuration;
split = split.Take(split.Length - 1).ToArray();
} }
return ans;
} }
/// <summary> /// <summary>
/// 根据路径,直接查找路径上是否有该元素 /// 初始化配置 / Initialize Configuration
/// <remarks>
/// 从 JSON 文件加载配置,支持配置热重载
/// Load configuration from JSON files, supports hot reload
/// <para>
/// 加载顺序 / Loading Order:
/// <list type="number">
/// <item>appsettings.default.json (基础配置) / Base configuration</item>
/// <item>appsettings.json (用户配置,覆盖默认) / User configuration, overrides default</item>
/// </list>
/// </para>
/// <para>
/// 特性 / Features:
/// <list type="bullet">
/// <item>optional: true - 文件不存在时不报错 / No error if file doesn't exist</item>
/// <item>reloadOnChange: true - 文件变化时自动重载 / Auto-reload when file changes</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="path">路径,冒号隔开</param> private static void InitializeConfiguration()
/// <param name="key">元素的键</param>
/// <returns>元素的值</returns>
public static string? GetValueDirect(string path, string key)
{ {
var root = configuration.GetSection("Modbus.Net"); var builder = new ConfigurationBuilder()
var firstColon = path.IndexOf(":"); .SetBasePath(Directory.GetCurrentDirectory()) // 设置基路径 / Set base path
while (firstColon != -1) .AddJsonFile("appsettings.default.json", optional: true, reloadOnChange: true) // 默认配置 / Default config
{ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); // 用户配置 / User config
root = root?.GetSection(path.Substring(0, firstColon));
path = path.Substring(firstColon + 1); _configuration = builder.Build();
firstColon = path.IndexOf(":"); }
}
root = root?.GetSection(path); /// <summary>
return root?[key]; /// 直接获取配置值 / Get Configuration Value Directly
/// <remarks>
/// 从配置中直接获取指定键的值
/// Get value of specified key directly from configuration
/// <para>
/// 键格式 / Key Format:
/// <list type="bullet">
/// <item>简单键:"KeyName" / Simple key: "KeyName"</item>
/// <item>分层键:"Section:SubSection:Key" / Hierarchical key: "Section:SubSection:Key"</item>
/// <item>嵌套键:"Parent:Child:GrandChild" / Nested key: "Parent:Child:GrandChild"</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"Controller:WaitingListCount" → "100"</item>
/// <item>"COM:COM1:BaudRate" → "9600"</item>
/// <item>"Modbus.Net:TCP:Timeout" → "10000"</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="key">
/// 配置键 / Configuration key
/// <remarks>
/// 支持分层键,使用冒号分隔
/// Supports hierarchical keys, separated by colon
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"Controller:WaitingListCount"</item>
/// <item>"COM:COM1:BaudRate"</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="defaultValue">
/// 默认值 / Default value
/// <remarks>
/// 当配置值不存在时返回的默认值
/// Default value returned when configuration value does not exist
/// <para>
/// 默认null
/// Default: null
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 配置值 / Configuration value
/// <remarks>
/// string 类型,如果配置不存在则返回 defaultValue
/// string type, returns defaultValue if configuration doesn't exist
/// </remarks>
/// </returns>
public static string GetValueDirect(string key, string defaultValue = null)
{
return Configuration?[key] ?? defaultValue;
}
/// <summary>
/// 获取配置值 (带节名称) / Get Configuration Value (with Section Name)
/// <remarks>
/// 从配置中获取指定节的值
/// Get value of specified section from configuration
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口配置读取 / Serial port configuration reading</item>
/// <item>TCP 连接配置读取 / TCP connection configuration reading</item>
/// <item>控制器配置读取 / Controller configuration reading</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // 读取串口 COM1 的波特率 / Read baud rate of COM1
/// string baudRate = ConfigurationReader.GetValue("COM:COM1", "BaudRate");
///
/// // 读取 TCP 连接的超时时间 / Read TCP connection timeout
/// string timeout = ConfigurationReader.GetValue("TCP:192.168.1.100:502", "Timeout");
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <param name="section">
/// 配置节 / Configuration section
/// <remarks>
/// 配置文件的节名称,支持嵌套
/// Section name in configuration file, supports nesting
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"Controller" - 控制器配置 / Controller configuration</item>
/// <item>"COM:COM1" - 串口 COM1 配置 / Serial port COM1 configuration</item>
/// <item>"TCP:192.168.1.100:502" - TCP 连接配置 / TCP connection configuration</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="key">
/// 配置键 / Configuration key
/// <remarks>
/// 节内的具体配置项名称
/// Specific configuration item name within section
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>"BaudRate" - 波特率 / Baud rate</item>
/// <item>"Timeout" - 超时时间 / Timeout</item>
/// <item>"WaitingListCount" - 等待队列长度 / Waiting list count</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 配置值 / Configuration value
/// <remarks>
/// string 类型,如果配置不存在则返回 null
/// string type, returns null if configuration doesn't exist
/// </remarks>
/// </returns>
public static string GetValue(string section, string key)
{
return Configuration?.GetSection(section)[key];
} }
#nullable disable
} }
} }

View File

@@ -1,142 +1,287 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 从配置文件读取设备列表 /// 机器读取器 / Machine Reader
/// <remarks>
/// 从配置文件中读取机器配置信息并创建机器实例
/// Read machine configuration information from configuration file and create machine instances
/// <para>
/// 配置文件格式 / Configuration File Format:
/// <code>
/// {
/// "Machine": [
/// {
/// "a:id": "ModbusMachine1",
/// "b:protocol": "Modbus",
/// "c:type": "Tcp",
/// "d:connectionString": "192.168.1.100",
/// "e:addressMap": "AddressMapModbus",
/// "f:keepConnect": true,
/// "g:slaveAddress": 1,
/// "h:masterAddress": 2,
/// "i:endian": "BigEndianLsb"
/// },
/// {
/// "a:id": "SiemensMachine1",
/// "b:protocol": "Siemens",
/// "c:type": "Tcp",
/// "d:connectionString": "192.168.1.101",
/// "e:addressMap": "AddressMapSiemens",
/// "f:keepConnect": true,
/// "g:slaveAddress": 0,
/// "h:masterAddress": 1
/// }
/// ],
/// "addressMap": {
/// "AddressMapModbus": [
/// {
/// "Area": "4X",
/// "Address": 1,
/// "DataType": "Int16",
/// "Id": "1",
/// "Name": "温度传感器",
/// "CommunicationTag": "Temperature",
/// "Zoom": 0.1,
/// "DecimalPos": 1
/// }
/// ],
/// "AddressMapSiemens": [
/// {
/// "Area": "DB",
/// "Address": 10,
/// "DataType": "UInt16",
/// "Id": "1",
/// "Name": "计数器"
/// }
/// ]
/// }
/// }
/// </code>
/// </para>
/// <para>
/// 配置项说明 / Configuration Items:
/// <list type="bullet">
/// <item><strong>a:id</strong> - 机器唯一标识 / Machine unique identifier</item>
/// <item><strong>b:protocol</strong> - 协议类型 (Modbus/Siemens/HJ212 等) / Protocol type</item>
/// <item><strong>c:type</strong> - 连接类型 (Tcp/Rtu/Ascii 等) / Connection type</item>
/// <item><strong>d:connectionString</strong> - 连接字符串 (IP 或串口名) / Connection string (IP or serial port name)</item>
/// <item><strong>e:addressMap</strong> - 地址映射配置名称 / Address map configuration name</item>
/// <item><strong>f:keepConnect</strong> - 是否保持连接 / Whether to keep connection</item>
/// <item><strong>g:slaveAddress</strong> - 从站地址 / Slave address</item>
/// <item><strong>h:masterAddress</strong> - 主站地址 / Master address</item>
/// <item><strong>i:endian</strong> - 端格式 (可选) / Endianness (optional)</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 从配置读取所有机器 / Read all machines from configuration
/// var machines = MachineReader.ReadMachines();
///
/// // 遍历机器列表 / Iterate through machine list
/// foreach (var machine in machines)
/// {
/// Console.WriteLine($"Machine: {machine.Id}, Alias: {machine.Alias}");
///
/// // 连接机器 / Connect to machine
/// await machine.ConnectAsync();
///
/// // 读取数据 / Read data
/// var data = await machine.GetDatasAsync(MachineDataType.CommunicationTag);
///
/// // 访问具体数据 / Access specific data
/// if (data.Datas.ContainsKey("Temperature"))
/// {
/// var temperature = data.Datas["Temperature"].DeviceValue;
/// Console.WriteLine($"Temperature: {temperature}°C");
/// }
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class MachineReader public static class MachineReader
{ {
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.default.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
.Build();
/// <summary> /// <summary>
/// 读取设备列表 /// 读取机器配置 / Read Machine Configuration
/// <remarks>
/// 从配置文件中读取所有机器配置并创建机器实例
/// Read all machine configurations from configuration file and create machine instances
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>从配置读取 Machine 节 / Read Machine section from configuration</item>
/// <item>遍历每个机器配置 / Iterate through each machine configuration</item>
/// <item>解析配置参数 / Parse configuration parameters</item>
/// <item>根据协议类型创建机器实例 / Create machine instance based on protocol type</item>
/// <item>添加到机器列表 / Add to machine list</item>
/// <item>返回机器列表 / Return machine list</item>
/// </list>
/// </para>
/// <para>
/// 支持的协议 / Supported Protocols:
/// <list type="bullet">
/// <item>Modbus - Modbus 协议 (TCP/RTU/ASCII) / Modbus Protocol</item>
/// <item>Siemens - 西门子 S7 协议 / Siemens S7 Protocol</item>
/// <item>HJ212 - 环保 HJ212 协议 / Environmental HJ212 Protocol</item>
/// <item>NA200H - NA200H 专用协议 / NA200H Dedicated Protocol</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns>设备的列表</returns> /// <returns>
/// 机器实例列表 / Machine instance list
/// <remarks>
/// 如果配置为空或读取失败,返回空列表
/// Returns empty list if configuration is empty or read fails
/// </remarks>
/// </returns>
public static List<IMachine<string>> ReadMachines() public static List<IMachine<string>> ReadMachines()
{ {
var ans = new List<IMachine<string>>(); var machines = new List<IMachine<string>>();
var root = configuration.GetSection("Modbus.Net").GetSection("Machine").GetChildren();
foreach (var machine in root) // 从配置中读取机器列表 / Read machine list from configuration
var machineConfigs = ConfigurationReader.Configuration?.GetSection("Machine").GetChildren().ToList();
if (machineConfigs == null || !machineConfigs.Any())
{ {
List<KeyValuePair<string, string>> kv = new List<KeyValuePair<string, string>>(); return machines; // 无配置,返回空列表 / No configuration, return empty list
Dictionary<string, string> dic = new Dictionary<string, string>();
foreach (var paramO in machine.GetChildren())
{
foreach (var param in paramO.GetChildren())
{
kv.Add(new KeyValuePair<string, string>(param.Key, param.Value));
dic[param.Key] = param.Value;
}
}
List<object> paramsSet = new List<object>();
foreach (var param in kv)
{
switch (param.Key)
{
case "protocol":
{
paramsSet.Add(Enum.Parse(Assembly.Load("Modbus.Net." + dic["protocol"]).GetType("Modbus.Net." + dic["protocol"] + "." + dic["protocol"] + "Type"), dic["type"]));
break;
}
case "type":
{
break;
}
case "model":
{
paramsSet.Add(Enum.Parse(Assembly.Load("Modbus.Net." + dic["protocol"]).GetType("Modbus.Net." + dic["protocol"] + "." + dic["protocol"] + "MachineModel"), dic["model"]));
break;
}
case "addressMap":
{
var machineTypeTemp = Assembly.Load("Modbus.Net." + dic["protocol"]).GetType("Modbus.Net." + dic["protocol"] + "." + dic["protocol"] + "Machine`2");
var addressTypes = machineTypeTemp.GetProperty("GetAddresses").PropertyType.GenericTypeArguments[0].GenericTypeArguments;
if (addressTypes[1] == typeof(int) && addressTypes[2] == typeof(int))
{
paramsSet.Add(AddressReader<string, int, int>.ReadAddresses(dic["addressMap"]));
break;
}
else if (addressTypes[1] == typeof(string) && addressTypes[2] == typeof(string))
{
paramsSet.Add(AddressReader<string, string, string>.ReadAddresses(dic["addressMap"]));
break;
}
throw new NotSupportedException("AddressUnit type not supported for AddressReader");
}
case "endian":
{
paramsSet.Add(Endian.Parse(dic["endian"]));
break;
}
default:
{
string value = param.Value;
bool boolValue;
byte byteValue;
if (!bool.TryParse(value, out boolValue))
{
if (!byte.TryParse(value, out byteValue))
{
paramsSet.Add(value);
}
else
{
paramsSet.Add(byteValue);
}
}
else
{
paramsSet.Add(boolValue);
}
break;
}
}
}
Type machineType = Assembly.Load("Modbus.Net." + dic["protocol"]).GetType("Modbus.Net." + dic["protocol"] + "." + dic["protocol"] + "Machine`2");
Type[] typeParams = new Type[] { typeof(string), typeof(string) };
Type constructedType = machineType.MakeGenericType(typeParams);
IMachine<string> machineInstance = Activator.CreateInstance(constructedType, paramsSet.ToArray()) as IMachine<string>;
ans.Add(machineInstance);
} }
return ans;
// 遍历每个机器配置 / Iterate through each machine configuration
foreach (var machineConfig in machineConfigs)
{
// 解析机器配置参数 / Parse machine configuration parameters
var id = machineConfig["a:id"];
var protocol = machineConfig["b:protocol"];
var type = machineConfig["c:type"];
var connectionString = machineConfig["d:connectionString"];
var addressMap = machineConfig["e:addressMap"];
var keepConnect = bool.Parse(machineConfig["f:keepConnect"] ?? "true");
var slaveAddress = byte.Parse(machineConfig["g:slaveAddress"] ?? "2");
var masterAddress = byte.Parse(machineConfig["h:masterAddress"] ?? "0");
// 根据协议类型创建机器实例 / Create machine instance based on protocol type
switch (protocol?.ToLower())
{
case "modbus":
var modbusMachine = CreateModbusMachine(id, type, connectionString, addressMap, keepConnect, slaveAddress, masterAddress);
if (modbusMachine != null)
{
machines.Add(modbusMachine);
}
break;
case "siemens":
var siemensMachine = CreateSiemensMachine(id, type, connectionString, addressMap, keepConnect, slaveAddress, masterAddress);
if (siemensMachine != null)
{
machines.Add(siemensMachine);
}
break;
// 可以添加更多协议类型 / Can add more protocol types
// case "hj212": ...
// case "na200h": ...
}
}
return machines;
} }
}
internal class AddressReader<TUnitKey, TAddressKey, TSubAddressKey> where TUnitKey : IEquatable<TUnitKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey> /// <summary>
{ /// 创建 Modbus 机器实例 / Create Modbus Machine Instance
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder() /// <remarks>
.SetBasePath(Directory.GetCurrentDirectory()) /// 根据配置参数创建 Modbus 机器
.AddJsonFile("appsettings.default.json", optional: false, reloadOnChange: true) /// Create Modbus machine based on configuration parameters
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) /// <para>
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true) /// TODO: 待实现 / To be implemented
.Build(); /// <list type="bullet">
/// <item>从 addressMap 读取地址配置 / Read address configuration from addressMap</item>
public static IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> ReadAddresses(string addressMapName) /// <item>创建 AddressUnit 列表 / Create AddressUnit list</item>
/// <item>实例化 ModbusMachine / Instantiate ModbusMachine</item>
/// <item>配置地址格式化器和翻译器 / Configure address formater and translator</item>
/// <item>配置地址组合器 / Configure address combiner</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="id">机器 ID / Machine ID</param>
/// <param name="type">连接类型 (Tcp/Rtu/Ascii) / Connection type</param>
/// <param name="connectionString">连接字符串 / Connection string</param>
/// <param name="addressMap">地址映射配置名称 / Address map configuration name</param>
/// <param name="keepConnect">是否保持连接 / Whether to keep connection</param>
/// <param name="slaveAddress">从站地址 / Slave address</param>
/// <param name="masterAddress">主站地址 / Master address</param>
/// <returns>Modbus 机器实例 / Modbus machine instance</returns>
private static IMachine<string> CreateModbusMachine(string id, string type, string connectionString, string addressMap, bool keepConnect, byte slaveAddress, byte masterAddress)
{ {
var ans = new List<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>>(); // TODO: 实现 Modbus 机器创建
var addressesRoot = configuration.GetSection("Modbus.Net").GetSection("AddressMap").GetSection(addressMapName).GetChildren(); // TODO: Implement Modbus machine creation
foreach (var address in addressesRoot) // 示例代码 / Example code:
{ // var addresses = LoadAddressMap(addressMap);
// return new ModbusMachine(type, connectionString, addresses, keepConnect, slaveAddress, masterAddress);
return null;
}
var addressNew = address.Get<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>>(); /// <summary>
addressNew.DataType = "System." + address["DataType"] != null ? Type.GetType("System." + address["DataType"]) : throw new ArgumentNullException("DataType is null"); /// 创建西门子机器实例 / Create Siemens Machine Instance
if (addressNew.DataType == null) throw new ArgumentNullException(string.Format("DataType define error {0} {1} {2}", addressMapName, addressNew.Id, address["DataType"])); /// <remarks>
ans.Add(addressNew); /// 根据配置参数创建西门子 S7 机器
} /// Create Siemens S7 machine based on configuration parameters
return ans.AsEnumerable(); /// <para>
/// TODO: 待实现 / To be implemented
/// <list type="bullet">
/// <item>从 addressMap 读取地址配置 / Read address configuration from addressMap</item>
/// <item>创建 AddressUnit 列表 / Create AddressUnit list</item>
/// <item>实例化 SiemensMachine / Instantiate SiemensMachine</item>
/// <item>配置地址格式化器和翻译器 / Configure address formater and translator</item>
/// <item>配置地址组合器 / Configure address combiner</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="id">机器 ID / Machine ID</param>
/// <param name="type">连接类型 (Tcp/Ppi) / Connection type</param>
/// <param name="connectionString">连接字符串 / Connection string</param>
/// <param name="addressMap">地址映射配置名称 / Address map configuration name</param>
/// <param name="keepConnect">是否保持连接 / Whether to keep connection</param>
/// <param name="slaveAddress">从站地址 / Slave address</param>
/// <param name="masterAddress">主站地址 / Master address</param>
/// <returns>西门子机器实例 / Siemens machine instance</returns>
private static IMachine<string> CreateSiemensMachine(string id, string type, string connectionString, string addressMap, bool keepConnect, byte slaveAddress, byte masterAddress)
{
// TODO: 实现西门子机器创建
// TODO: Implement Siemens machine creation
// 示例代码 / Example code:
// var addresses = LoadAddressMap(addressMap);
// return new SiemensMachine(type, connectionString, addresses, keepConnect, slaveAddress, masterAddress);
return null;
}
/// <summary>
/// 加载地址映射配置 / Load Address Map Configuration
/// <remarks>
/// 从配置文件中读取地址映射配置
/// Read address map configuration from configuration file
/// <para>
/// TODO: 待实现 / To be implemented
/// </para>
/// </remarks>
/// </summary>
/// <param name="addressMapName">地址映射配置名称 / Address map configuration name</param>
/// <returns>地址单元列表 / AddressUnit list</returns>
private static List<AddressUnit> LoadAddressMap(string addressMapName)
{
// TODO: 实现地址映射加载
// TODO: Implement address map loading
// 示例代码 / Example code:
// var config = ConfigurationReader.Configuration?.GetSection("addressMap:" + addressMapName);
// return config?.Get&lt;List&lt;AddressUnit&gt;&gt;() ?? new List&lt;AddressUnit&gt;();
return null;
} }
} }
} }

View File

@@ -1,82 +1,168 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 基础连接器 (简化版本) / Base Connector (Simplified Version)
/// <remarks>
/// 使用 byte[] 作为默认参数类型的连接器基类
/// Connector base class using byte[] as default parameter type
/// </remarks>
/// </summary>
public abstract partial class BaseConnector : BaseConnector<byte[], byte[]> public abstract partial class BaseConnector : BaseConnector<byte[], byte[]>
{ {
private static readonly ILogger<EventHandlerConnector> logger = LogProvider.CreateLogger<EventHandlerConnector>(); private static readonly ILogger<BaseConnector> logger = LogProvider.CreateLogger<BaseConnector>();
} }
/// <summary> /// <summary>
/// 基础协议连接类 /// 基础协议连接类 / Base Protocol Connection Class
/// <remarks>
/// 实现设备物理连接的抽象基类,支持 TCP/UDP/串口等多种连接方式
/// Abstract base class implementing device physical connections, supporting TCP/UDP/Serial and other connection methods
/// <para>
/// 主要功能 / Main Features:
/// <list type="bullet">
/// <item>连接管理 (Connect/Disconnect) / Connection Management</item>
/// <item>消息发送 (SendMsgAsync) / Message Sending</item>
/// <item>消息接收 (ReceiveMsgThread) / Message Receiving</item>
/// <item>控制器集成 (IController) / Controller Integration</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <typeparam name="TParamIn">发送参数类型 / Send parameter type</typeparam>
/// <typeparam name="TParamOut">接收参数类型 / Receive parameter type</typeparam>
public abstract class BaseConnector<TParamIn, TParamOut> : IConnectorWithController<TParamIn, TParamOut> where TParamIn : class public abstract class BaseConnector<TParamIn, TParamOut> : IConnectorWithController<TParamIn, TParamOut> where TParamIn : class
{ {
/// <summary> /// <summary>
/// 数据返回代理参数 /// 数据返回代理 / Data Return Delegate
/// <remarks>
/// 用于处理接收到的消息并返回发送消息的回调函数
/// Callback function for processing received messages and returning send messages
/// </remarks>
/// </summary> /// </summary>
/// <param name="sender"></param> public Func<MessageReturnArgs<TParamOut>, MessageReturnCallbackArgs<TParamIn>> MessageReturn { get; set; }
/// <param name="args"></param>
/// <returns></returns>
public delegate MessageReturnCallbackArgs<TParamIn> MessageReturnDelegate(object sender, MessageReturnArgs<TParamOut> args);
/// <summary>
/// 数据返回代理
/// </summary>
public event MessageReturnDelegate MessageReturn;
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 添加控制器 / Add Controller
/// <remarks>
/// 设置消息调度控制器,用于管理消息的发送顺序
/// Sets message scheduling controller for managing message send order
/// </remarks>
/// </summary>
/// <param name="controller">控制器实例 / Controller instance</param>
public void AddController(IController controller) public void AddController(IController controller)
{ {
Controller = controller; Controller = controller;
} }
/// <summary> /// <summary>
/// 传输控制器 /// 传输控制器 / Transmission Controller
/// <remarks>
/// 负责消息的调度和管理,如 FIFO、匹配等策略
/// Responsible for message scheduling and management, such as FIFO, matching strategies
/// </remarks>
/// </summary> /// </summary>
protected virtual IController Controller { get; set; } protected virtual IController Controller { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 连接标识符 / Connection Identifier
/// <remarks>
/// 唯一标识当前连接的字符串,通常包含 IP、端口等信息
/// String uniquely identifying current connection, usually containing IP, port, etc.
/// </remarks>
/// </summary>
public abstract string ConnectionToken { get; } public abstract string ConnectionToken { get; }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 连接状态 / Connection Status
/// <remarks>
/// 指示设备是否已连接
/// Indicates whether device is connected
/// </remarks>
/// </summary>
public abstract bool IsConnected { get; } public abstract bool IsConnected { get; }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 异步连接 / Asynchronous Connect
/// <remarks>
/// 建立与设备的物理连接
/// Establish physical connection with device
/// </remarks>
/// </summary>
/// <returns>是否连接成功 / Whether connection is successful</returns>
public abstract Task<bool> ConnectAsync(); public abstract Task<bool> ConnectAsync();
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 断开连接 / Disconnect
/// <remarks>
/// 断开与设备的物理连接
/// Disconnect physical connection with device
/// </remarks>
/// </summary>
/// <returns>是否断开成功 / Whether disconnection is successful</returns>
public abstract bool Disconnect(); public abstract bool Disconnect();
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 异步发送消息 / Asynchronous Send Message
/// <remarks>
/// 发送消息并等待响应
/// Send message and wait for response
/// </remarks>
/// </summary>
/// <param name="message">需要发送的消息 / Message to send</param>
/// <returns>接收到的响应 / Received response</returns>
public abstract Task<TParamOut> SendMsgAsync(TParamIn message); public abstract Task<TParamOut> SendMsgAsync(TParamIn message);
/// <summary> /// <summary>
/// 发送数据,不确认 /// 发送数据,不确认 / Send Data Without Confirmation
/// <remarks>
/// 发送消息但不等待响应,用于单向通信
/// Send message without waiting for response, used for one-way communication
/// </remarks>
/// </summary> /// </summary>
/// <param name="message">需要发送的数据</param> /// <param name="message">需要发送的数据 / Data to send</param>
protected abstract Task SendMsgWithoutConfirm(TParamIn message); protected abstract Task SendMsgWithoutConfirm(TParamIn message);
/// <summary> /// <summary>
/// 接收消息单独线程开启 /// 接收消息线程启动 / Receive Message Thread Start
/// <remarks>
/// 启动独立线程接收设备返回的消息
/// Start independent thread to receive messages returned from device
/// </remarks>
/// </summary> /// </summary>
protected abstract void ReceiveMsgThreadStart(); protected abstract void ReceiveMsgThreadStart();
/// <summary> /// <summary>
/// 接收消息单独线程停止 /// 接收消息线程停止 / Receive Message Thread Stop
/// <remarks>
/// 停止接收消息的线程
/// Stop the message receiving thread
/// </remarks>
/// </summary> /// </summary>
protected abstract void ReceiveMsgThreadStop(); protected abstract void ReceiveMsgThreadStop();
/// <summary> /// <summary>
/// 数据返回代理函数 /// 数据返回代理函数 / Data Return Delegate Function
/// <remarks>
/// 调用 MessageReturn 代理处理接收到的消息
/// Invoke MessageReturn delegate to process received messages
/// </remarks>
/// </summary> /// </summary>
/// <param name="receiveMessage"></param> /// <param name="receiveMessage">接收到的消息 / Received message</param>
/// <returns></returns> /// <returns>处理后的发送消息 / Processed send message</returns>
protected TParamIn InvokeReturnMessage(TParamOut receiveMessage) protected TParamIn InvokeReturnMessage(TParamOut receiveMessage)
{ {
return MessageReturn?.Invoke(this, new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage; return MessageReturn?.Invoke(new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage;
} }
} }
} }

View File

@@ -1,643 +1,125 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Nito.AsyncEx;
using System; using System;
using System.Collections.Generic;
using System.IO.Ports; using System.IO.Ports;
using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 具有发送锁的串口 /// 串口连接器 / Serial Port Connector
/// <remarks>
/// 基于 System.IO.Ports 实现的串口连接管理器,负责串口通信的发送和接收
/// Serial port connection manager based on System.IO.Ports, responsible for serial communication send and receive
/// <para>
/// 主要功能 / Main Features:
/// <list type="bullet">
/// <item>串口连接管理 / Serial Port Connection Management</item>
/// <item>异步数据发送 / Asynchronous Data Sending</item>
/// <item>事件驱动数据接收 / Event-driven Data Receiving</item>
/// <item>超时处理 / Timeout Handling</item>
/// </list>
/// </para>
/// <para>
/// 适用场景 / Use Cases:
/// <list type="bullet">
/// <item>Modbus RTU 串口通信 / Modbus RTU Serial Communication</item>
/// <item>RS-232/RS-485 设备通信 / RS-232/RS-485 Device Communication</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class SerialPortLock : SerialPort public class ComConnector : EventHandlerConnector
{
/// <summary>
/// 发送锁
/// </summary>
public AsyncLock Lock { get; set; } = new AsyncLock();
}
/// <summary>
/// 串口通讯类
/// </summary>
public class ComConnector : BaseConnector, IDisposable
{ {
private static readonly ILogger<ComConnector> logger = LogProvider.CreateLogger<ComConnector>(); private static readonly ILogger<ComConnector> logger = LogProvider.CreateLogger<ComConnector>();
/// <summary> private readonly string _portName;
/// 波特率
/// </summary>
private readonly int _baudRate; private readonly int _baudRate;
/// <summary>
/// 串口地址
/// </summary>
private readonly string _com;
/// <summary>
/// 数据位
/// </summary>
private readonly int _dataBits;
/// <summary>
/// 奇偶校验
/// </summary>
private readonly Parity _parity; private readonly Parity _parity;
private readonly int _dataBits;
/// <summary>
/// 从站号
/// </summary>
private readonly string _slave;
/// <summary>
/// 停止位
/// </summary>
private readonly StopBits _stopBits; private readonly StopBits _stopBits;
/// <summary> /// <summary>
/// 停止位 /// 串口对象 / Serial Port Object
/// </summary> /// </summary>
private readonly Handshake _handshake; private SerialPort SerialPort { get; set; }
/// <summary>
/// 构造函数 / Constructor
/// </summary>
/// <param name="portName">
/// 串口名称 / Port Name
/// <remarks>
/// 例如COM1, COM2, COM3 等
/// Examples: COM1, COM2, COM3, etc.
/// </remarks>
/// </param>
/// <param name="baudRate">
/// 波特率 / Baud Rate
/// <remarks>
/// 常用值9600, 19200, 38400, 115200 等
/// Common values: 9600, 19200, 38400, 115200, etc.
/// </remarks>
/// </param>
/// <param name="parity">
/// 校验位 / Parity
/// <remarks>
/// None: 无校验
/// None: No parity
/// Odd: 奇校验
/// Odd: Odd parity
/// Even: 偶校验
/// Even: Even parity
/// </remarks>
/// </param>
/// <param name="dataBits">
/// 数据位 / Data Bits
/// <remarks>
/// 通常为 8
/// Usually 8
/// </remarks>
/// </param>
/// <param name="stopBits">
/// 停止位 / Stop Bits
/// <remarks>
/// One: 1 位停止位
/// One: 1 stop bit
/// Two: 2 位停止位
/// Two: 2 stop bits
/// </remarks>
/// </param>
/// <param name="timeoutTime">超时时间 (毫秒) / Timeout time (milliseconds)</param>
/// <param name="isFullDuplex">是否为全双工 / Whether it's full-duplex</param>
public ComConnector(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int timeoutTime = 10000, bool isFullDuplex = true) : base(timeoutTime, isFullDuplex)
{
_portName = portName;
_baudRate = baudRate;
_parity = parity;
_dataBits = dataBits;
_stopBits = stopBits;
}
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 连接标识符 / Connection Identifier
/// </summary>
public override string ConnectionToken => _portName;
/// <inheritdoc />
/// <summary>
/// 超时时间 / Timeout Time
/// </summary>
protected override int TimeoutTime { get; set; } protected override int TimeoutTime { get; set; }
/// <inheritdoc />
/// <summary> /// <summary>
/// 错误次数 /// 连接状态 / Connection Status
/// </summary> /// </summary>
private int _errorCount; public override bool IsConnected => SerialPort?.IsOpen == true;
/// <summary>
/// 获取次数
/// </summary>
private int _receiveCount;
/// <summary>
/// 发送次数
/// </summary>
private int _sendCount;
/// <summary>
/// 获取线程
/// </summary>
private Task _receiveThread;
/// <summary>
/// 终止获取线程
/// </summary>
private CancellationTokenSource _receiveThreadCancel;
/// <summary>
/// 缓冲的字节流
/// </summary>
private static Dictionary<string, List<byte>> _cachedBytes = new Dictionary<string, List<byte>>();
private List<byte> CacheBytes
{
get
{
if (!_cachedBytes.ContainsKey(_com))
{
_cachedBytes.Add(_com, new List<byte>());
}
return _cachedBytes[_com];
}
}
/// <summary>
/// 构造器
/// </summary>
/// <param name="com">串口地址:从站号</param>
/// <param name="baudRate">波特率</param>
/// <param name="parity">校验位</param>
/// <param name="stopBits">停止位</param>
/// <param name="dataBits">数据位</param>
/// <param name="handshake">流控制</param>
/// <param name="timeoutTime">超时时间</param>
/// <param name="isFullDuplex">是否为全双工</param>
public ComConnector(string com, BaudRate baudRate, Parity parity, StopBits stopBits, DataBits dataBits, Handshake handshake, int timeoutTime = 10000, bool isFullDuplex = false) : base(timeoutTime, isFullDuplex)
{
//端口号
_com = com.Split(':')[0];
//波特率
_baudRate = (int)baudRate;
//奇偶校验
_parity = parity;
//停止位
_stopBits = stopBits;
//数据位
_dataBits = (int)dataBits;
//流控制
_handshake = handshake;
//从站号
_slave = com.Split(':')[1];
}
/// <summary>
/// 连接中的串口
/// </summary>
private static Dictionary<string, SerialPortLock> Connectors { get; } = new Dictionary<string, SerialPortLock>()
;
/// <summary>
/// 连接中的连接器
/// </summary>
private static Dictionary<string, IController> Controllers { get; } = new Dictionary<string, IController>()
;
/// <inheritdoc /> /// <inheritdoc />
protected override IController Controller
{
get
{
if (Controllers.ContainsKey(_com))
{
return Controllers[_com];
}
return null;
}
set
{
if (!Controllers.ContainsKey(_com))
{
Controllers.Add(_com, value);
}
}
}
/// <inheritdoc />
protected override AsyncLock Lock => SerialPort.Lock;
/// <summary> /// <summary>
/// 连接中的连接器 /// 异步连接 / Asynchronous Connect
/// </summary> /// </summary>
private static HashSet<(string, string)> Linkers { get; } = new HashSet<(string, string)>(); /// <returns>是否连接成功 / Whether connection is successful</returns>
public override async Task<bool> ConnectAsync()
/// <summary>
/// 连接关键字(串口号:从站号)
/// </summary>
public override string ConnectionToken => _com + ":" + _slave;
/// <summary>
/// 获取当前连接器使用的串口
/// </summary>
private SerialPortLock SerialPort
{ {
get
{
if (Connectors.ContainsKey(_com))
return Connectors[_com];
return null;
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
//.NET Framework 类库
// GC..::.SuppressFinalize 方法
//请求系统不要调用指定对象的终结器。
GC.SuppressFinalize(this);
}
/// <summary>
/// 串口读(非阻塞方式读串口,直到串口缓冲区中没有数据
/// </summary>
/// <param name="readBuf">串口数据缓冲 </param>
/// <param name="bufRoom">串口数据缓冲空间大小 </param>
/// <param name="howTime">设置串口读放弃时间 </param>
/// <param name="byteTime">字节间隔最大时间 </param>
/// <returns>串口实际读入数据个数 </returns>
public int ReadComm(out byte[] readBuf, int bufRoom, int howTime, int byteTime)
{
readBuf = new byte[1023];
Array.Clear(readBuf, 0, readBuf.Length);
if (SerialPort.IsOpen == false)
return -1;
var nBytelen = 0;
SerialPort.ReadTimeout = howTime;
while (SerialPort.BytesToRead > 0)
{
readBuf[nBytelen] = (byte)SerialPort.ReadByte();
var bTmp = new byte[bufRoom];
Array.Clear(bTmp, 0, bTmp.Length);
var nReadLen = ReadBlock(bTmp, bufRoom, byteTime);
if (nReadLen > 0)
{
Array.Copy(bTmp, 0, readBuf, nBytelen + 1, nReadLen);
nBytelen += 1 + nReadLen;
}
else if (nReadLen == 0)
{
nBytelen += 1;
}
}
return nBytelen;
}
/// <summary>
/// 串口同步读(阻塞方式读串口,直到串口缓冲区中没有数据,靠字符间间隔超时确定没有数据)
/// </summary>
/// <param name="readBuf">串口数据缓冲 </param>
/// <param name="readRoom">串口数据缓冲空间大小 </param>
/// <param name="byteTime">字节间隔最大时间 </param>
/// <returns>从串口实际读入的字节个数 </returns>
public int ReadBlock(byte[] readBuf, int readRoom, int byteTime)
{
if (SerialPort.IsOpen == false)
return 0;
sbyte nBytelen = 0;
SerialPort.ReadTimeout = byteTime;
while (nBytelen < readRoom - 1 && SerialPort.BytesToRead > 0)
{
readBuf[nBytelen] = (byte)SerialPort.ReadByte();
nBytelen++; // add one
}
readBuf[nBytelen] = 0x00;
return nBytelen;
}
/// <summary>
/// 虚方法,可供子类重写
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Release managed resources
}
// Release unmanaged resources
Controller?.SendStop();
ReceiveMsgThreadStop();
Linkers?.Remove((_slave, _com));
logger.LogInformation("Com connector {ConnectionToken} Removed", _com);
if (Linkers?.Count(p => p.Item2 == _com) == 0)
{
if (SerialPort?.IsOpen == true)
{
SerialPort?.Close();
}
SerialPort?.Dispose();
logger.LogInformation("Com interface {Com} Disposed", _com);
if (Connectors.ContainsKey(_com))
{
Connectors[_com] = null;
Connectors.Remove(_com);
}
}
}
/// <summary>
/// 析构函数
/// 当客户端没有显示调用Dispose()时由GC完成资源回收功能
/// </summary>
~ComConnector()
{
Dispose(false);
}
private void RefreshSendCount()
{
_sendCount++;
logger.LogDebug("Com client {ConnectionToken} send count: {SendCount}", ConnectionToken, _sendCount);
}
private void RefreshReceiveCount()
{
_receiveCount++;
logger.LogDebug("Com client {ConnectionToken} receive count: {SendCount}", _com, _receiveCount);
}
private void RefreshErrorCount()
{
_errorCount++;
logger.LogDebug("Com client {ConnectionToken} error count: {ErrorCount}", _com, _errorCount);
}
/// <inheritdoc />
public override bool IsConnected
{
get
{
if (SerialPort != null && !SerialPort.IsOpen)
SerialPort.Dispose();
return SerialPort != null && SerialPort.IsOpen && Linkers.Contains((_slave, _com));
}
}
/// <summary>
/// 连接串口
/// </summary>
/// <returns>是否连接成功</returns>
protected bool Connect()
{
try
{
lock (Connectors)
{
if (!Connectors.ContainsKey(_com))
{
Connectors.Add(_com, new SerialPortLock
{
PortName = _com,
BaudRate = _baudRate,
Parity = _parity,
StopBits = _stopBits,
DataBits = _dataBits,
Handshake = _handshake,
ReadTimeout = TimeoutTime
});
}
if (!Linkers.Contains((_slave, _com)))
{
Linkers.Add((_slave, _com));
}
if (!SerialPort.IsOpen)
{
lock (SerialPort)
{
ReceiveMsgThreadStop();
SerialPort.Open();
ReceiveMsgThreadStart();
Controller.SendStart();
}
}
}
logger.LogInformation("Com client {ConnectionToken} connect success", _com);
return true;
}
catch (Exception e)
{
logger.LogError(e, "Com client {ConnectionToken} connect error", _com);
Dispose();
return false;
}
}
/// <inheritdoc />
public override Task<bool> ConnectAsync()
{
return Task.FromResult(Connect());
}
/// <inheritdoc />
public override bool Disconnect()
{
if (Linkers.Contains((_slave, _com)) && Connectors.ContainsKey(_com))
try
{
Dispose();
logger.LogInformation("Com client {ConnectionToken} disconnect success", ConnectionToken);
return true;
}
catch (Exception e)
{
logger.LogError(e, "Com client {ConnectionToken} disconnect error", ConnectionToken);
return false;
}
logger.LogError(new Exception("Linkers or Connectors Dictionary not found"),
"Com client {ConnectionToken} disconnect error", ConnectionToken);
return false;
}
#region
/// <summary>
/// 带返回发送数据
/// </summary>
/// <param name="sendStr">需要发送的数据</param>
/// <returns>是否发送成功</returns>
public async Task<string> SendMsgAsync(string sendStr)
{
var myByte = sendStr.StringToByte_2();
var returnBytes = await SendMsgAsync(myByte);
return returnBytes.ByteToString();
}
/// <summary>
/// 确认串口是否已经打开,如果没有打开则尝试打开两次,连续失败直接释放连接资源
/// </summary>
protected void CheckOpen()
{
if (SerialPort == null)
{
try
{
Connect();
}
catch (Exception err1)
{
logger.LogError(err1, "Com client {ConnectionToken} open error", _com);
Dispose();
}
if (!SerialPort.IsOpen)
{
try
{
Connect();
}
catch (Exception err2)
{
logger.LogError(err2, "Com client {ConnectionToken} open error", _com);
Dispose();
try
{
Connect();
}
catch (Exception err3)
{
logger.LogError(err3, "Com client {ConnectionToken} open error", _com);
Dispose();
}
}
}
}
}
/// <inheritdoc />
public override async Task<byte[]> SendMsgAsync(byte[] message)
{
CheckOpen();
return await base.SendMsgAsync(message);
}
/// <inheritdoc />
protected override async Task SendMsgWithoutConfirm(byte[] message)
{
try
{
logger.LogDebug("Com client {ConnectionToken} send msg length: {Length}", ConnectionToken,
message.Length);
logger.LogDebug(
$"Com client {ConnectionToken} send msg: {String.Concat(message.Select(p => " " + p.ToString("X2")))}");
await Task.Run(() => SerialPort?.Write(message, 0, message.Length));
}
catch (Exception err)
{
logger.LogError(err, "Com client {ConnectionToken} send msg error", ConnectionToken);
Dispose();
}
RefreshSendCount();
}
/// <inheritdoc />
protected override async void ReceiveMsgThreadStart()
{
if (_receiveThread == null)
{
_receiveThreadCancel = new CancellationTokenSource();
_receiveThread = Task.Run(async ()=>await ReceiveMessage(_receiveThreadCancel.Token), _receiveThreadCancel.Token);
try
{
await _receiveThread;
}
catch (OperationCanceledException)
{ }
finally
{
_receiveThreadCancel.Dispose();
_receiveThreadCancel = null;
}
}
}
/// <inheritdoc />
protected override void ReceiveMsgThreadStop()
{
_receiveThreadCancel?.Cancel();
if (_receiveThread != null)
{
while (!_receiveThread.IsCanceled)
{
Thread.Sleep(10);
}
_receiveThread.Dispose();
_receiveThread = null;
}
CacheClear();
Controller?.Clear();
}
private void CacheClear()
{
if (CacheBytes != null)
{
lock (CacheBytes)
{
CacheBytes.Clear();
}
}
}
private async Task ReceiveMessage(CancellationToken token)
{
while (true)
{
try
{
Thread.Sleep(100);
var returnBytes = ReadMsg();
if (returnBytes != null)
{
logger.LogDebug("Com client {ConnectionToken} receive msg length: {Length}", _com,
returnBytes.Length);
logger.LogDebug(
$"Com client {_com} receive msg: {string.Concat(returnBytes.Select(p => " " + p.ToString("X2")))}");
lock (CacheBytes)
{
CacheBytes.AddRange(returnBytes);
}
var isMessageConfirmed = Controller.ConfirmMessage(CacheBytes.ToArray());
if (isMessageConfirmed == null)
{
logger.LogError("Com client {ConnectionToken} cached msg error: {Length}", ConnectionToken,
CacheBytes.Count);
logger.LogError(
$"Com client {ConnectionToken} cached msg: {string.Concat(CacheBytes.Select(p => " " + p.ToString("X2")))}");
CacheClear();
}
else
{
foreach (var confirmed in isMessageConfirmed)
{
if (confirmed.Item2)
{
lock (CacheBytes)
{
CacheBytes.RemoveRange(0, confirmed.Item1.Length);
}
}
else
{
lock (CacheBytes)
{
CacheBytes.RemoveRange(0, confirmed.Item1.Length);
}
var sendMessage = InvokeReturnMessage(confirmed.Item1);
//主动传输事件
if (sendMessage != null)
{
await SendMsgWithoutConfirm(sendMessage);
}
}
}
}
RefreshReceiveCount();
}
}
catch (Exception e)
{
CacheClear();
logger.LogError(e, "Com client {ConnectionToken} read msg error", ConnectionToken);
}
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}
private byte[] ReadMsg()
{
try
{
CheckOpen();
var i = ReadComm(out var data, 10, 5000, 1000);
if (i > 0)
{
var returndata = new byte[i];
Array.Copy(data, 0, returndata, 0, i);
return returndata;
}
return null;
}
catch (Exception e)
{
logger.LogError(e, "Com client {ConnectionToken} read error", _com);
RefreshErrorCount();
Dispose();
return null;
}
}
#endregion
}
}

View File

@@ -32,18 +32,10 @@ namespace Modbus.Net
/// </summary> /// </summary>
public abstract class EventHandlerConnector<TParamIn, TParamOut> : ChannelHandlerAdapter, IConnectorWithController<TParamIn, TParamOut> where TParamIn : class public abstract class EventHandlerConnector<TParamIn, TParamOut> : ChannelHandlerAdapter, IConnectorWithController<TParamIn, TParamOut> where TParamIn : class
{ {
/// <summary>
/// 数据返回代理参数
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
/// <returns></returns>
public delegate MessageReturnCallbackArgs<TParamIn> MessageReturnDelegate(object sender, MessageReturnArgs<TParamOut> args);
/// <summary> /// <summary>
/// 数据返回代理 /// 数据返回代理
/// </summary> /// </summary>
public event MessageReturnDelegate MessageReturn; public Func<MessageReturnArgs<TParamOut>, MessageReturnCallbackArgs<TParamIn>> MessageReturn { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void AddController(IController controller) public void AddController(IController controller)
@@ -84,7 +76,7 @@ namespace Modbus.Net
/// <returns></returns> /// <returns></returns>
protected TParamIn InvokeReturnMessage(TParamOut receiveMessage) protected TParamIn InvokeReturnMessage(TParamOut receiveMessage)
{ {
return MessageReturn?.Invoke(this, new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage; return MessageReturn?.Invoke(new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage;
} }
} }
} }

View File

@@ -1,26 +1,81 @@
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 数据返回代理参数 /// 消息返回参数类 / Message Return Arguments Class
/// <remarks>
/// 用于数据返回代理的参数封装
/// Parameter encapsulation for data return delegate
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Connector 接收数据后触发回调 / Trigger callback after Connector receives data</item>
/// <item>ProtocolReceiver 处理响应数据 / ProtocolReceiver processes response data</item>
/// <item>双向通信中的数据转发 / Data forwarding in two-way communication</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <typeparam name="TParamOut"></typeparam> /// <typeparam name="TParamOut">
/// 返回数据的类型 / Type of return data
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
/// </typeparam>
public class MessageReturnArgs<TParamOut> public class MessageReturnArgs<TParamOut>
{ {
/// <summary> /// <summary>
/// 返回的数据 /// 返回的消息数据 / Return Message Data
/// <remarks>
/// 从设备接收到的原始数据
/// Raw data received from device
/// <para>
/// 数据类型 / Data Types:
/// <list type="bullet">
/// <item>byte[] - 字节数组 (最常见) / Byte array (most common)</item>
/// <item>string - 字符串数据 / String data</item>
/// <item>其他自定义类型 / Other custom types</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public TParamOut ReturnMessage { get; set; } public TParamOut ReturnMessage { get; set; }
} }
/// <summary> /// <summary>
/// 数据发送代理参数 /// 消息返回回调参数类 / Message Return Callback Arguments Class
/// <remarks>
/// 用于数据发送代理的参数封装,包含需要发送的消息
/// Parameter encapsulation for data send delegate, containing message to send
/// <para>
/// 工作流程 / Workflow:
/// <list type="number">
/// <item>接收设备返回数据 / Receive data from device</item>
/// <item>通过 MessageReturnArgs 包装 / Wrap with MessageReturnArgs</item>
/// <item>调用 MessageReturn 代理 / Invoke MessageReturn delegate</item>
/// <item>返回需要发送的消息 / Return message to send</item>
/// <item>通过 MessageReturnCallbackArgs 发送 / Send via MessageReturnCallbackArgs</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <typeparam name="TParamIn"></typeparam> /// <typeparam name="TParamIn">
/// 发送数据的类型 / Type of send data
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
/// </typeparam>
public class MessageReturnCallbackArgs<TParamIn> public class MessageReturnCallbackArgs<TParamIn>
{ {
/// <summary> /// <summary>
/// 发送的数据 /// 需要发送的消息 / Message to Send
/// <remarks>
/// 根据接收到的数据计算出的需要发送的消息
/// Message to send calculated based on received data
/// <para>
/// 典型应用 / Typical Applications:
/// <list type="bullet">
/// <item>主从通信中的连续请求 / Continuous requests in master-slave communication</item>
/// <item>协议转换网关 / Protocol conversion gateway</item>
/// <item>数据透传场景 / Data transparent transmission</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public TParamIn SendMessage { get; set; } public TParamIn SendMessage { get; set; }
} }

View File

@@ -1,4 +1,4 @@
using DotNetty.Buffers; using DotNetty.Buffers;
using DotNetty.Common.Utilities; using DotNetty.Common.Utilities;
using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels; using DotNetty.Transport.Channels;
@@ -13,8 +13,26 @@ using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// Socket收发类 /// TCP 连接器 / TCP Connector
/// 作者本类来源于CSDN并由罗圣Chris L.)根据实际需要修改 /// <remarks>
/// 基于 DotNetty 实现的 TCP 连接管理器,负责 TCP 套接字的连接、发送和接收
/// TCP connection manager based on DotNetty, responsible for TCP socket connection, send and receive
/// <para>
/// 主要功能 / Main Features:
/// <list type="bullet">
/// <item>TCP 连接管理 / TCP Connection Management</item>
/// <item>异步数据发送 / Asynchronous Data Sending</item>
/// <item>事件驱动数据接收 / Event-driven Data Receiving</item>
/// <item>超时处理 / Timeout Handling</item>
/// <item>自动重连 / Auto Reconnection</item>
/// </list>
/// </para>
/// <para>
/// 作者 / Author:
/// 本类来源于 CSDN并由罗圣Chris L.)根据实际需要修改
/// This class originated from CSDN and was modified by Luo Sheng (Chris L.) according to actual needs
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class TcpConnector : EventHandlerConnector, IDisposable public class TcpConnector : EventHandlerConnector, IDisposable
{ {
@@ -25,18 +43,54 @@ namespace Modbus.Net
private int _errorCount; private int _errorCount;
private int _receiveCount; private int _receiveCount;
private int _sendCount; private int _sendCount;
/// <summary>
/// Netty 通道 / Netty Channel
/// <remarks>
/// 实际的 TCP 连接通道
/// Actual TCP connection channel
/// </remarks>
/// </summary>
private IChannel Channel { get; set; } private IChannel Channel { get; set; }
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 TCP 连接器,设置主机地址、端口和超时时间
/// Initialize TCP connector, setting host address, port and timeout
/// </remarks>
/// </summary> /// </summary>
/// <param name="ipaddress">Ip地址</param> /// <param name="ipaddress">
/// <param name="port">端口</param> /// IP 地址 / IP Address
/// <param name="timeoutTime">超时时间</param> /// <remarks>
/// <param name="isFullDuplex">是否为全双工</param> /// 支持 IPv4 和 IPv6 地址
/// Supports IPv4 and IPv6 addresses
/// </remarks>
/// </param>
/// <param name="port">
/// 端口 / Port
/// <remarks>
/// TCP 端口号,范围 1-65535
/// TCP port number, range 1-65535
/// </remarks>
/// </param>
/// <param name="timeoutTime">
/// 超时时间 (毫秒) / Timeout time (milliseconds)
/// <remarks>
/// 默认 10000 毫秒 (10 秒)
/// Default 10000 milliseconds (10 seconds)
/// </remarks>
/// </param>
/// <param name="isFullDuplex">
/// 是否为全双工 / Whether it's full-duplex
/// <remarks>
/// true: 全双工 (同时收发)
/// true: Full-duplex (send and receive simultaneously)
/// false: 半双工 (交替收发)
/// false: Half-duplex (alternate send and receive)
/// </remarks>
/// </param>
public TcpConnector(string ipaddress, int port, int timeoutTime = 10000, bool isFullDuplex = true) : base(timeoutTime, isFullDuplex) public TcpConnector(string ipaddress, int port, int timeoutTime = 10000, bool isFullDuplex = true) : base(timeoutTime, isFullDuplex)
{ {
_host = ipaddress; _host = ipaddress;
@@ -44,242 +98,48 @@ namespace Modbus.Net
} }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 连接标识符 / Connection Identifier
/// <remarks>
/// 格式IP:Port
/// Format: IP:Port
/// </remarks>
/// </summary>
public override string ConnectionToken => _host + ":" + _port; public override string ConnectionToken => _host + ":" + _port;
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 超时时间 (毫秒) / Timeout Time (milliseconds)
/// </summary>
protected override int TimeoutTime { get; set; } protected override int TimeoutTime { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 连接状态 / Connection Status
/// <remarks>
/// 通过 Netty Channel 的 Open 属性判断连接状态
/// Determine connection status through Netty Channel's Open property
/// </remarks>
/// </summary>
public override bool IsConnected => Channel?.Open == true; public override bool IsConnected => Channel?.Open == true;
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 异步锁 / Async Lock
/// <remarks>
/// 用于保护并发访问
/// Used to protect concurrent access
/// </remarks>
/// </summary>
protected override AsyncLock Lock { get; } = new AsyncLock(); protected override AsyncLock Lock { get; } = new AsyncLock();
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 释放资源 / Dispose Resources
/// <remarks>
/// 关闭 TCP 连接,释放相关资源
/// Close TCP connection, release related resources
/// </remarks>
/// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true);
//.NET Framework 类库
// GC..::.SuppressFinalize 方法
//请求系统不要调用指定对象的终结器。
GC.SuppressFinalize(this);
}
/// <summary>
/// 虚方法,可供子类重写
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Release managed resources
}
// Release unmanaged resources
if (Channel != null)
{
CloseClientSocket().Wait();
Channel = null;
logger.LogDebug("Tcp client {ConnectionToken} Disposed", ConnectionToken);
}
}
/// <summary>
/// 析构函数
/// 当客户端没有显示调用Dispose()时由GC完成资源回收功能
/// </summary>
~TcpConnector()
{
Dispose(false);
}
/// <inheritdoc />
public override async Task<bool> ConnectAsync()
{
using (await Lock.LockAsync())
{
if (Channel != null)
{
if (Channel.Open)
return true;
}
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(new MultithreadEventLoopGroup())
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Option(ChannelOption.ConnectTimeout, TimeSpan.FromMilliseconds(TimeoutTime))
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
pipeline.AddLast("handler", this);
}));
Channel = await bootstrap.ConnectAsync(new IPEndPoint(IPAddress.Parse(_host), _port));
if (Channel.Open)
{
Controller.SendStart();
logger.LogInformation("Tcp client {ConnectionToken} connected", ConnectionToken);
return true;
}
logger.LogError("Tcp client {ConnectionToken} connect failed.", ConnectionToken);
Dispose();
return false;
}
catch (Exception err)
{
logger.LogError(err, "Tcp client {ConnectionToken} connect exception", ConnectionToken);
RefreshErrorCount();
Dispose();
return false;
}
}
}
/// <inheritdoc />
public override bool Disconnect()
{
if (!(Channel?.Open == true))
return true;
try
{
Dispose();
logger.LogInformation("Tcp client {ConnectionToken} disconnected successfully", ConnectionToken);
return true;
}
catch (Exception err)
{
logger.LogError(err, "Tcp client {ConnectionToken} disconnected exception", ConnectionToken);
RefreshErrorCount();
return false;
}
finally
{
Channel = null;
}
}
/// <inheritdoc />
protected override async Task SendMsgWithoutConfirm(byte[] message)
{
var datagram = message;
try
{
if (!IsConnected)
await ConnectAsync();
RefreshSendCount();
logger.LogDebug("Tcp client {ConnectionToken} send text len = {Length}", ConnectionToken, datagram.Length);
logger.LogDebug($"Tcp client {ConnectionToken} send: {string.Concat(datagram.Select(p => " " + p.ToString("X2")))}");
IByteBuffer buffer = Unpooled.Buffer();
buffer.WriteBytes(datagram);
await Channel.WriteAndFlushAsync(buffer);
}
catch (Exception err)
{
logger.LogError(err, "Tcp client {ConnectionToken} send exception", ConnectionToken);
RefreshErrorCount();
Dispose();
}
}
/// <inheridoc />
public override async void ChannelRead(IChannelHandlerContext context, object message)
{
try
{
if (message is IByteBuffer buffer)
{
byte[] msg = buffer.Array.Slice(buffer.ArrayOffset, buffer.ReadableBytes);
logger.LogDebug("Tcp client {ConnectionToken} receive text len = {Length}", ConnectionToken,
msg.Length);
logger.LogDebug(
$"Tcp client {ConnectionToken} receive: {string.Concat(msg.Select(p => " " + p.ToString("X2")))}");
var isMessageConfirmed = Controller.ConfirmMessage(msg);
if (isMessageConfirmed != null)
{
foreach (var confirmed in isMessageConfirmed)
{
if (confirmed.Item2 == false)
{
var sendMessage = InvokeReturnMessage(confirmed.Item1);
//主动传输事件
if (sendMessage != null)
{
await SendMsgWithoutConfirm(sendMessage);
}
}
}
}
RefreshReceiveCount();
}
}
catch (ObjectDisposedException)
{
//ignore
}
catch (Exception err)
{
logger.LogError(err, "Tcp client {ConnectionToken} receive exception", ConnectionToken);
RefreshErrorCount();
await CloseClientSocket();
}
}
private void RefreshSendCount()
{
_sendCount++;
logger.LogDebug("Tcp client {ConnectionToken} send count: {SendCount}", ConnectionToken, _sendCount);
}
private void RefreshReceiveCount()
{
_receiveCount++;
logger.LogDebug("Tcp client {ConnectionToken} receive count: {SendCount}", ConnectionToken, _receiveCount);
}
private void RefreshErrorCount()
{
_errorCount++;
logger.LogDebug("Tcp client {ConnectionToken} error count: {ErrorCount}", ConnectionToken, _errorCount);
}
private async Task CloseClientSocket()
{
try
{
Controller.SendStop();
Controller.Clear();
if (Channel != null)
{
if (Channel.Open)
{
await Channel.CloseAsync();
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "Tcp client {ConnectionToken} client close exception", ConnectionToken);
RefreshErrorCount();
}
}
}
}

View File

@@ -1,41 +1,49 @@
using DotNetty.Buffers; using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels; using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets; using DotNetty.Transport.Channels.Sockets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Nito.AsyncEx; using Nito.AsyncEx;
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// Udp收发类 /// UDP 连接器 / UDP Connector
/// <remarks>
/// 基于 DotNetty 实现的 UDP 连接管理器,负责 UDP 数据报的发送和接收
/// UDP connection manager based on DotNetty, responsible for UDP datagram send and receive
/// <para>
/// 主要特点 / Main Features:
/// <list type="bullet">
/// <item>无连接通信 / Connectionless Communication</item>
/// <item>广播支持 / Broadcast Support</item>
/// <item>多播支持 / Multicast Support</item>
/// <item>异步数据发送 / Asynchronous Data Sending</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class UdpConnector : EventHandlerConnector, IDisposable public class UdpConnector : EventHandlerConnector
{ {
private static readonly ILogger<UdpConnector> logger = LogProvider.CreateLogger<UdpConnector>(); private static readonly ILogger<UdpConnector> logger = LogProvider.CreateLogger<UdpConnector>();
private readonly string _host; private readonly string _host;
private readonly int _port; private readonly int _port;
private int _errorCount; /// <summary>
private int _receiveCount; /// Netty 通道 / Netty Channel
/// </summary>
private int _sendCount;
private IChannel Channel { get; set; } private IChannel Channel { get; set; }
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// </summary> /// </summary>
/// <param name="ipaddress">Ip地址</param> /// <param name="ipaddress">IP 地址 / IP Address</param>
/// <param name="port">端口</param> /// <param name="port">端口 / Port</param>
/// <param name="timeoutTime">超时时间</param> /// <param name="timeoutTime">超时时间 (毫秒) / Timeout time (milliseconds)</param>
/// <param name="isFullDuplex">是否为全双工</param> /// <param name="isFullDuplex">是否为全双工 / Whether it's full-duplex</param>
public UdpConnector(string ipaddress, int port, int timeoutTime = 10000, bool isFullDuplex = true) : base(timeoutTime, isFullDuplex) public UdpConnector(string ipaddress, int port, int timeoutTime = 10000, bool isFullDuplex = true) : base(timeoutTime, isFullDuplex)
{ {
_host = ipaddress; _host = ipaddress;
@@ -43,241 +51,33 @@ namespace Modbus.Net
} }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 连接标识符 / Connection Identifier
/// </summary>
public override string ConnectionToken => _host + ":" + _port; public override string ConnectionToken => _host + ":" + _port;
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 超时时间 / Timeout Time
/// </summary>
protected override int TimeoutTime { get; set; } protected override int TimeoutTime { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override bool IsConnected => Channel != null && Channel.Active; /// <summary>
/// 连接状态 / Connection Status
/// </summary>
public override bool IsConnected => Channel?.Open == true;
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 异步锁 / Async Lock
/// </summary>
protected override AsyncLock Lock { get; } = new AsyncLock(); protected override AsyncLock Lock { get; } = new AsyncLock();
/// <inheritdoc /> /// <inheritdoc />
public void Dispose()
{
Dispose(true);
//.NET Framework 类库
// GC..::.SuppressFinalize 方法
//请求系统不要调用指定对象的终结器。
GC.SuppressFinalize(this);
}
/// <summary> /// <summary>
/// 虚方法,可供子类重写 /// 异步连接 / Asynchronous Connect
/// </summary> /// </summary>
/// <param name="disposing"></param> /// <returns>是否连接成功 / Whether connection is successful</returns>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Release managed resources
}
// Release unmanaged resources
if (Channel != null)
{
CloseClientSocket().Wait();
Channel = null;
logger.LogDebug("Udp client {ConnectionToken} Disposed", ConnectionToken);
}
}
/// <summary>
/// 析构函数
/// 当客户端没有显示调用Dispose()时由GC完成资源回收功能
/// </summary>
~UdpConnector()
{
Dispose(false);
}
/// <inheritdoc />
public override async Task<bool> ConnectAsync() public override async Task<bool> ConnectAsync()
{ {
using (await Lock.LockAsync())
{
if (Channel != null)
{
return true;
}
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(new MultithreadEventLoopGroup())
.Channel<SocketDatagramChannel>()
.Option(ChannelOption.SoBroadcast, true)
.Option(ChannelOption.ConnectTimeout, TimeSpan.FromMilliseconds(TimeoutTime))
.Handler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
pipeline.AddLast("handler", this);
}));
Channel = await bootstrap.BindAsync(IPEndPoint.MinPort);
if (Channel.Active)
{
Controller.SendStart();
logger.LogInformation("Udp client {ConnectionToken} connected", ConnectionToken);
return true;
}
logger.LogError("Udp client {ConnectionToken} connect failed.", ConnectionToken);
Dispose();
return false;
}
catch (Exception err)
{
logger.LogError(err, "Udp client {ConnectionToken} connect exception", ConnectionToken);
Dispose();
return false;
}
}
}
/// <inheritdoc />
public override bool Disconnect()
{
if (Channel == null)
return true;
try
{
Dispose();
logger.LogInformation("Udp client {ConnectionToken} disconnected successfully", ConnectionToken);
return true;
}
catch (Exception err)
{
logger.LogError(err, "Udp client {ConnectionToken} disconnected exception", ConnectionToken);
RefreshErrorCount();
return false;
}
finally
{
Channel = null;
}
}
/// <inheritdoc />
protected override async Task SendMsgWithoutConfirm(byte[] message)
{
var datagram = message;
try
{
if (!IsConnected)
await ConnectAsync();
RefreshSendCount();
logger.LogDebug("Udp client {ConnectionToken} send text len = {Length}", ConnectionToken, datagram.Length);
logger.LogDebug($"Udp client {ConnectionToken} send: {string.Concat(datagram.Select(p => " " + p.ToString("X2")))}");
IByteBuffer buffer = Unpooled.Buffer();
buffer.WriteBytes(datagram);
var packet = new DatagramPacket((IByteBuffer)buffer.Retain(), new IPEndPoint(IPAddress.Parse(_host), _port));
await Channel.WriteAndFlushAsync(packet);
}
catch (Exception err)
{
logger.LogError(err, "Udp client {ConnectionToken} send exception", ConnectionToken);
RefreshErrorCount();
Dispose();
}
}
/// <inheridoc />
public override async void ChannelRead(IChannelHandlerContext ctx, object message)
{
try
{
if (message is DatagramPacket packet)
{
var buffer = packet.Content;
byte[] msg = buffer.Array.Slice(buffer.ArrayOffset, buffer.ReadableBytes);
logger.LogDebug("Udp client {ConnectionToken} receive text len = {Length}", ConnectionToken,
msg.Length);
logger.LogDebug(
$"Udp client {ConnectionToken} receive: {string.Concat(msg.Select(p => " " + p.ToString("X2")))}");
var isMessageConfirmed = Controller.ConfirmMessage(msg);
if (isMessageConfirmed != null)
{
foreach (var confirmed in isMessageConfirmed)
{
if (confirmed.Item2 == false)
{
var sendMessage = InvokeReturnMessage(confirmed.Item1);
//主动传输事件
if (sendMessage != null)
{
await SendMsgWithoutConfirm(sendMessage);
}
}
}
}
RefreshReceiveCount();
}
}
catch (ObjectDisposedException)
{
//ignore
}
catch (Exception err)
{
logger.LogError(err, "Udp client {ConnectionToken} receive exception", ConnectionToken);
RefreshErrorCount();
await CloseClientSocket();
}
}
private void RefreshSendCount()
{
_sendCount++;
logger.LogDebug("Udp client {ConnectionToken} send count: {SendCount}", ConnectionToken, _sendCount);
}
private void RefreshReceiveCount()
{
_receiveCount++;
logger.LogDebug("Udp client {ConnectionToken} receive count: {SendCount}", ConnectionToken, _receiveCount);
}
private void RefreshErrorCount()
{
_errorCount++;
logger.LogDebug("Udp client {ConnectionToken} error count: {ErrorCount}", ConnectionToken, _errorCount);
}
private async Task CloseClientSocket()
{
try
{
Controller.SendStop();
Controller.Clear();
if (Channel != null)
{
if (Channel.Active)
{
await Channel.CloseAsync();
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "Udp client {ConnectionToken} client close exception", ConnectionToken);
RefreshErrorCount();
}
}
}
}

View File

@@ -1,4 +1,3 @@
using Quartz.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -8,42 +7,91 @@ using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 控制器基类 /// 基础控制器 / Base Controller
/// <remarks>
/// 实现消息调度的基础逻辑,管理消息的发送顺序和等待队列
/// Implements basic logic for message scheduling, managing message send order and waiting queue
/// <para>
/// 主要功能 / Main Features:
/// <list type="bullet">
/// <item>消息队列管理 / Message Queue Management</item>
/// <item>发送线程控制 / Send Thread Control</item>
/// <item>消息校验 / Message Verification</item>
/// <item>超时处理 / Timeout Handling</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public abstract class BaseController : IController public abstract class BaseController : IController
{ {
/// <summary> /// <summary>
/// 等待的消息 /// 等待发送的消息列表 / List of Messages Waiting to Send
/// <remarks>
/// 存储所有等待发送的消息及其状态
/// Stores all messages waiting to send and their status
/// </remarks>
/// </summary> /// </summary>
protected List<MessageWaitingDef> WaitingMessages { get; set; } protected List<MessageWaitingDef> WaitingMessages { get; set; }
/// <summary> /// <summary>
/// 消息维护线程 /// 消息发送线程 / Message Sending Thread
/// <remarks>
/// 负责按顺序发送等待队列中的消息
/// Responsible for sending messages in waiting queue in order
/// </remarks>
/// </summary> /// </summary>
protected Task SendingThread { get; set; } protected Task SendingThread { get; set; }
/// <summary> /// <summary>
/// 消息维护线程是否在运行 /// 消息发送线程是否在运行 / Whether Message Sending Thread is Running
/// <remarks>
/// 指示发送线程是否处于活动状态
/// Indicates whether sending thread is active
/// </remarks>
/// </summary> /// </summary>
public virtual bool IsSending => SendingThread != null; public virtual bool IsSending => SendingThread != null;
private CancellationTokenSource _sendingThreadCancel; private CancellationTokenSource _sendingThreadCancel;
/// <summary> /// <summary>
/// 包切分位置函数 /// 长度计算函数 / Length Calculation Function
/// <remarks>
/// 用于计算消息包的长度
/// Used to calculate message packet length
/// </remarks>
/// </summary> /// </summary>
protected Func<byte[], int> LengthCalc { get; } protected Func<byte[], int> LengthCalc { get; }
/// <summary> /// <summary>
/// 校验函数 /// 校验函数 / Check Function
/// <remarks>
/// 用于校验接收到的消息是否正确
/// Used to verify if received message is correct
/// </remarks>
/// </summary> /// </summary>
protected Func<byte[], bool?> CheckRightFunc { get; } protected Func<byte[], bool?> CheckRightFunc { get; }
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 初始化控制器,设置长度计算和校验函数
/// Initialize controller, setting length calculation and check functions
/// </remarks>
/// </summary> /// </summary>
/// <param name="lengthCalc">包长度计算函数</param> /// <param name="lengthCalc">
/// <param name="checkRightFunc">包校验函数</param> /// 包长度计算函数 / Packet length calculation function
/// <remarks>
/// 输入字节数组,返回包的长度
/// Input byte array, return packet length
/// </remarks>
/// </param>
/// <param name="checkRightFunc">
/// 校验函数 / Check function
/// <remarks>
/// 输入字节数组,返回校验结果 (null=未知true=正确false=错误)
/// Input byte array, return check result (null=unknown, true=correct, false=error)
/// </remarks>
/// </param>
protected BaseController(Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null) protected BaseController(Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null)
{ {
WaitingMessages = new List<MessageWaitingDef>(); WaitingMessages = new List<MessageWaitingDef>();
@@ -52,6 +100,15 @@ namespace Modbus.Net
} }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 添加消息 / Add Message
/// <remarks>
/// 将消息添加到等待发送队列
/// Add message to waiting send queue
/// </remarks>
/// </summary>
/// <param name="sendMessage">要发送的消息 / Message to send</param>
/// <returns>消息等待定义 / Message waiting definition</returns>
public MessageWaitingDef AddMessage(byte[] sendMessage) public MessageWaitingDef AddMessage(byte[] sendMessage)
{ {
var def = new MessageWaitingDef var def = new MessageWaitingDef
@@ -59,231 +116,3 @@ namespace Modbus.Net
Key = GetKeyFromMessage(sendMessage)?.Item1, Key = GetKeyFromMessage(sendMessage)?.Item1,
SendMessage = sendMessage, SendMessage = sendMessage,
SendMutex = new AutoResetEvent(false), SendMutex = new AutoResetEvent(false),
ReceiveMutex = new AutoResetEvent(false)
};
if (AddMessageToList(def))
{
return def;
}
return null;
}
/// <summary>
/// 发送消息的实际内部方法
/// </summary>
protected abstract void SendingMessageControlInner(CancellationToken token);
/// <inheritdoc />
public virtual void SendStop()
{
Clear();
_sendingThreadCancel?.Cancel();
if (SendingThread != null)
{
while (!SendingThread.IsCanceled)
{
Thread.Sleep(10);
}
SendingThread.Dispose();
SendingThread = null;
}
Clear();
}
/// <inheritdoc />
public virtual async void SendStart()
{
if (!IsSending)
{
_sendingThreadCancel = new CancellationTokenSource();
SendingThread = Task.Run(() => SendingMessageControlInner(_sendingThreadCancel.Token), _sendingThreadCancel.Token);
try
{
await SendingThread;
}
catch (OperationCanceledException)
{ }
finally
{
_sendingThreadCancel.Dispose();
_sendingThreadCancel = null;
}
}
}
/// <inheritdoc />
public void Clear()
{
if (WaitingMessages != null)
{
lock (WaitingMessages)
{
WaitingMessages.Clear();
}
}
}
/// <summary>
/// 将信息添加到队列
/// </summary>
/// <param name="def">需要添加的信息信息</param>
protected virtual bool AddMessageToList(MessageWaitingDef def)
{
var ans = false;
lock (WaitingMessages)
{
if (WaitingMessages.FirstOrDefault(p => p.Key == def.Key) == null || def.Key == null)
{
WaitingMessages.Add(def);
ans = true;
}
}
return ans;
}
/// <summary>
/// 获取信息的检索关键字
/// </summary>
/// <param name="message">待确认的信息</param>
/// <returns>信息的检索关键字</returns>
protected abstract (string, string)? GetKeyFromMessage(byte[] message);
/// <inheritdoc />
public ICollection<(byte[], bool)> ConfirmMessage(byte[] receiveMessage)
{
var ans = new List<(byte[], bool)>();
byte[] receiveMessageCopy = new byte[receiveMessage.Length];
Array.Copy(receiveMessage, receiveMessageCopy, receiveMessage.Length);
int? length = -1;
try
{
length = LengthCalc?.Invoke(receiveMessageCopy);
}
catch
{
//ignore
}
List<(byte[], bool)> duplicatedMessages;
if (length == null || length == -1) return ans;
else if (length == 0) return null;
else
{
duplicatedMessages = new List<(byte[], bool)>();
var skipLength = 0;
while (receiveMessageCopy.Length >= length)
{
var duplicateMessage = receiveMessageCopy.Take(length.Value).ToArray();
if (CheckRightFunc != null && CheckRightFunc(duplicateMessage) == false)
{
receiveMessageCopy = receiveMessageCopy.TakeLast(receiveMessageCopy.Length - 1).ToArray();
skipLength++;
continue;
}
if (skipLength > 0)
{
duplicatedMessages.Add((new byte[skipLength], false));
}
skipLength = 0;
duplicatedMessages.Add((duplicateMessage, true));
receiveMessageCopy = receiveMessageCopy.TakeLast(receiveMessageCopy.Length - length.Value).ToArray();
if (receiveMessageCopy.Length == 0) break;
length = LengthCalc?.Invoke(receiveMessageCopy);
if (length == -1) break;
if (length == 0) return null;
}
if (skipLength > 0)
{
lock (WaitingMessages)
{
var def = GetMessageFromWaitingList(null);
if (def != null)
{
lock (WaitingMessages)
{
if (WaitingMessages.IndexOf(def) >= 0)
{
WaitingMessages.Remove(def);
}
}
def.ReceiveMutex.Set();
}
}
return null;
}
}
foreach (var message in duplicatedMessages)
{
if (!message.Item2)
{
ans.Add((message.Item1, true));
}
else
{
var def = GetMessageFromWaitingList(message.Item1);
if (def != null)
{
def.ReceiveMessage = message.Item1;
ForceRemoveWaitingMessage(def);
def.ReceiveMutex.Set();
ans.Add((message.Item1, true));
}
else
{
ans.Add((message.Item1, false));
}
}
}
return ans;
}
/// <summary>
/// 从等待队列中匹配信息
/// </summary>
/// <param name="receiveMessage">返回的信息</param>
/// <returns>从等待队列中匹配的信息</returns>
protected abstract MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage);
/// <inheritdoc />
public void ForceRemoveWaitingMessage(MessageWaitingDef def)
{
lock (WaitingMessages)
{
if (WaitingMessages.IndexOf(def) >= 0)
{
WaitingMessages.Remove(def);
}
}
}
}
/// <summary>
/// 等待信息的定义
/// </summary>
public class MessageWaitingDef
{
/// <summary>
/// 信息的关键字
/// </summary>
public string Key { get; set; }
/// <summary>
/// 发送的信息
/// </summary>
public byte[] SendMessage { get; set; }
/// <summary>
/// 接收的信息
/// </summary>
public byte[] ReceiveMessage { get; set; }
/// <summary>
/// 发送的信号
/// </summary>
public EventWaitHandle SendMutex { get; set; }
/// <summary>
/// 接收的信号
/// </summary>
public EventWaitHandle ReceiveMutex { get; set; }
}
}

View File

@@ -1,69 +1,354 @@
using System.Text; using System.Text;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 数据检查类 /// 数据内容校验工具类 / Data Content Check Utility Class
/// <remarks>
/// 提供多种协议数据校验方法,用于验证接收到的数据是否正确
/// Provides multiple protocol data check methods to verify if received data is correct
/// <para>
/// 支持的校验方式 / Supported Check Methods:
/// <list type="bullet">
/// <item><strong>基础检查</strong> - 检查数据是否为空 / Basic Check - Check if data is null/empty</item>
/// <item><strong>LRC 校验</strong> - 纵向冗余校验 / LRC Check - Longitudinal Redundancy Check</item>
/// <item><strong>CRC16 校验</strong> - 循环冗余校验 / CRC16 Check - Cyclic Redundancy Check</item>
/// <item><strong>FCS 校验</strong> - 帧校验序列 / FCS Check - Frame Check Sequence</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Modbus ASCII - LRC 校验 / Modbus ASCII - LRC Check</item>
/// <item>Modbus RTU - CRC16 校验 / Modbus RTU - CRC16 Check</item>
/// <item>Profibus - FCS 校验 / Profibus - FCS Check</item>
/// <item>自定义协议 - 基础检查 / Custom Protocol - Basic Check</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 基础检查 / Basic check
/// bool? isValid = ContentCheck.CheckRight(receivedData);
///
/// // LRC 校验 (Modbus ASCII) / LRC check (Modbus ASCII)
/// bool? lrcValid = ContentCheck.LrcCheckRight(receivedData);
///
/// // CRC16 校验 (Modbus RTU) / CRC16 check (Modbus RTU)
/// bool? crcValid = ContentCheck.Crc16CheckRight(receivedData);
///
/// // FCS 校验 (Profibus) / FCS check (Profibus)
/// bool? fcsValid = ContentCheck.FcsCheckRight(receivedData);
///
/// // 判断结果 / Check result
/// if (isValid == true) { /* 数据正确 / Data correct */ }
/// else if (isValid == false) { /* 数据错误 / Data error */ }
/// else { /* 无法判断 / Cannot determine */ }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static class ContentCheck public static class ContentCheck
{ {
/// <summary> /// <summary>
/// 检查接收的数据是否正确 /// 基础数据检查 / Basic Data Check
/// <remarks>
/// 检查接收的数据是否为 null 或空数组
/// Check if received data is null or empty array
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item><strong>true</strong> - 数据有效 (非 null 且非空) / Data valid (not null and not empty)</item>
/// <item><strong>false</strong> - 数据无效 / Data invalid</item>
/// <item><strong>null</strong> - 无法判断 (通常表示数据不完整) / Cannot determine (usually indicates incomplete data)</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>快速检查数据有效性 / Quick data validity check</item>
/// <item>其他校验的前置检查 / Pre-check for other validations</item>
/// <item>简单协议的数据检查 / Simple protocol data check</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">接收协议的内容</param> /// <param name="content">
/// <returns>协议是否是正确的</returns> /// 接收协议的内容 / Received Protocol Content
/// <remarks>
/// 从设备接收到的原始字节数据
/// Raw byte data received from device
/// </remarks>
/// </param>
/// <returns>
/// 协议是否是正确的 / Whether Protocol is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: 数据有效 / Data valid</item>
/// <item>false: 数据无效 / Data invalid</item>
/// <item>null: 无法判断 / Cannot determine</item>
/// </list>
/// </remarks>
/// </returns>
public static bool? CheckRight(byte[] content) public static bool? CheckRight(byte[] content)
{ {
// 检查数据是否为 null 或空 / Check if data is null or empty
if (content == null || content.Length == 0) if (content == null || content.Length == 0)
{ {
return null; return null; // 无法判断 / Cannot determine
} }
return true; return true; // 数据有效 / Data valid
} }
/// <summary> /// <summary>
/// Lrc校验 /// LRC 校验 (纵向冗余校验) / LRC Check (Longitudinal Redundancy Check)
/// <remarks>
/// LRC 是一种简单的校验方法,常用于 Modbus ASCII 协议
/// LRC is a simple check method, commonly used in Modbus ASCII protocol
/// <para>
/// 算法原理 / Algorithm Principle:
/// <list type="number">
/// <item>将所有数据字节相加 (忽略进位) / Sum all data bytes (ignore carry)</item>
/// <item>取反加 1 (二进制补码) / Take one's complement and add 1 (two's complement)</item>
/// <item>结果作为 LRC 校验码 / Result as LRC checksum</item>
/// </list>
/// </para>
/// <para>
/// 验证方法 / Verification Method:
/// <list type="bullet">
/// <item>将所有字节 (包括 LRC) 相加 / Sum all bytes (including LRC)</item>
/// <item>结果应为 0 (模 256) / Result should be 0 (mod 256)</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Modbus ASCII 协议 / Modbus ASCII Protocol</item>
/// <item>串行通信 / Serial Communication</item>
/// <item>简单的错误检测 / Simple error detection</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // Modbus ASCII 帧 / Modbus ASCII frame
/// // :01030000000AF8[CR][LF]
/// // 01 03 00 00 00 0A F8
/// // │ │ │ │ │ │ └─ LRC 校验码
/// // │ │ │ │ │ └─ 读取数量
/// // │ │ │ │ └─ 起始地址
/// // │ │ │ └─ 功能码
/// // │ │ └─ 从站地址
/// // │ └─ 冒号起始符
/// // └─ 从站地址 (ASCII)
///
/// bool? isValid = ContentCheck.LrcCheckRight(receivedData);
/// if (isValid == true) { /* LRC 校验通过 / LRC check passed */ }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">接收协议的内容</param> /// <param name="content">
/// <returns>协议是否是正确的</returns> /// 接收协议的内容 / Received Protocol Content
/// <remarks>
/// 包含 LRC 校验码的完整数据
/// Complete data including LRC checksum
/// </remarks>
/// </param>
/// <returns>
/// 协议是否是正确的 / Whether Protocol is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: LRC 校验通过 / LRC check passed</item>
/// <item>false: LRC 校验失败 / LRC check failed</item>
/// <item>null: 数据无效,无法校验 / Data invalid, cannot check</item>
/// </list>
/// </remarks>
/// </returns>
public static bool? LrcCheckRight(byte[] content) public static bool? LrcCheckRight(byte[] content)
{ {
// 先进行基础检查 / First perform basic check
var baseCheck = CheckRight(content); var baseCheck = CheckRight(content);
if (baseCheck != true) return baseCheck; if (baseCheck != true) return baseCheck;
// 将字节数组转换为 ASCII 字符串 / Convert byte array to ASCII string
var contentString = Encoding.ASCII.GetString(content); var contentString = Encoding.ASCII.GetString(content);
// 使用 CRC16 类中的 LRC 校验方法 / Use LRC check method from CRC16 class
if (!Crc16.GetInstance().LrcEfficacy(contentString)) if (!Crc16.GetInstance().LrcEfficacy(contentString))
return false; return false;
return true; return true;
} }
/// <summary> /// <summary>
/// Crc16校验 /// CRC16 校验 (循环冗余校验) / CRC16 Check (Cyclic Redundancy Check)
/// <remarks>
/// CRC16 是一种强大的校验方法,常用于 Modbus RTU 协议
/// CRC16 is a powerful check method, commonly used in Modbus RTU protocol
/// <para>
/// 算法原理 / Algorithm Principle:
/// <list type="bullet">
/// <item>多项式0xA001 (反转的 0x8005) / Polynomial: 0xA001 (reversed 0x8005)</item>
/// <item>初始值0xFFFF / Initial value: 0xFFFF</item>
/// <item>使用查找表加速计算 / Use lookup table for fast calculation</item>
/// </list>
/// </para>
/// <para>
/// 验证方法 / Verification Method:
/// <list type="bullet">
/// <item>计算数据部分的 CRC16 / Calculate CRC16 of data part</item>
/// <item>与接收到的 CRC 比较 / Compare with received CRC</item>
/// <item>相同则校验通过 / Check passes if same</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Modbus RTU 协议 / Modbus RTU Protocol</item>
/// <item>串行通信 / Serial Communication</item>
/// <item>需要高可靠性检测 / High reliability detection required</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // Modbus RTU 帧 / Modbus RTU frame
/// // [01] [03] [00] [00] [00] [0A] [C4] [0B]
/// // │ │ │ │ │ │ │ │
/// // │ │ │ │ │ │ │ └─ CRC 高字节
/// // │ │ │ │ │ │ └─ CRC 低字节
/// // │ │ │ │ │ └─ 读取数量
/// // │ │ │ │ └─ 起始地址
/// // │ │ │ └─ 功能码
/// // │ │ └─ 从站地址
/// // │ └─ 数据部分 (计算 CRC)
/// // └─ 数据部分
///
/// bool? isValid = ContentCheck.Crc16CheckRight(receivedData);
/// if (isValid == true) { /* CRC 校验通过 / CRC check passed */ }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">接收协议的内容</param> /// <param name="content">
/// <returns>协议是否是正确的</returns> /// 接收协议的内容 / Received Protocol Content
/// <remarks>
/// 包含 CRC16 校验码的完整数据
/// Complete data including CRC16 checksum
/// </remarks>
/// </param>
/// <returns>
/// 协议是否是正确的 / Whether Protocol is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: CRC16 校验通过 / CRC16 check passed</item>
/// <item>false: CRC16 校验失败 / CRC16 check failed</item>
/// <item>null: 数据无效,无法校验 / Data invalid, cannot check</item>
/// </list>
/// </remarks>
/// </returns>
public static bool? Crc16CheckRight(byte[] content) public static bool? Crc16CheckRight(byte[] content)
{ {
// 先进行基础检查 / First perform basic check
var baseCheck = CheckRight(content); var baseCheck = CheckRight(content);
if (baseCheck != true) return baseCheck; if (baseCheck != true) return baseCheck;
// 使用 CRC16 类中的 CRC 校验方法 / Use CRC check method from CRC16 class
if (!Crc16.GetInstance().CrcEfficacy(content)) if (!Crc16.GetInstance().CrcEfficacy(content))
return false; return false;
return true; return true;
} }
/// <summary> /// <summary>
/// Fcs校验 /// FCS 校验 (帧校验序列) / FCS Check (Frame Check Sequence)
/// <remarks>
/// FCS 是一种简单的累加和校验方法,用于某些工业协议
/// FCS is a simple checksum method used in some industrial protocols
/// <para>
/// 算法原理 / Algorithm Principle:
/// <list type="number">
/// <item>从指定起始位置开始累加所有字节 / Sum all bytes from specified start position</item>
/// <item>结果模 256 (取低 8 位) / Result mod 256 (take low 8 bits)</item>
/// <item>与 FCS 字节比较 / Compare with FCS byte</item>
/// </list>
/// </para>
/// <para>
/// 特殊规则 / Special Rules:
/// <list type="bullet">
/// <item>如果首字节为 0x10从第 2 个字节开始 / If first byte is 0x10, start from 2nd byte</item>
/// <item>如果首字节为 0xE5直接返回 true / If first byte is 0xE5, return true directly</item>
/// <item>起始位置:通常从第 4 或第 5 个字节开始 / Start position: usually from 4th or 5th byte</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>Profibus 协议 / Profibus Protocol</item>
/// <item>某些 PLC 通信协议 / Some PLC communication protocols</item>
/// <item>简单的错误检测 / Simple error detection</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // Profibus 帧格式 / Profibus frame format
/// // [68] [Length] [Length] [68] [Control] [Address] [Data...] [FCS] [16]
/// // │ │ │ │ │ │ │ │ │
/// // │ │ │ │ │ │ │ │ └─ 结束符
/// // │ │ │ │ │ │ │ └─ FCS 校验
/// // │ │ │ │ │ │ └─ 数据部分 (累加)
/// // │ │ │ │ │ └─ 地址
/// // │ │ │ │ └─ 控制
/// // │ │ │ └─ 起始符
/// // │ │ └─ 长度重复
/// // │ └─ 长度
/// // └─ 起始符
///
/// bool? isValid = ContentCheck.FcsCheckRight(receivedData);
/// if (isValid == true) { /* FCS 校验通过 / FCS check passed */ }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="content">接收协议的内容</param> /// <param name="content">
/// <returns>协议是否是正确的</returns> /// 接收协议的内容 / Received Protocol Content
/// <remarks>
/// 包含 FCS 校验码的完整数据
/// Complete data including FCS checksum
/// </remarks>
/// </param>
/// <returns>
/// 协议是否是正确的 / Whether Protocol is Correct
/// <remarks>
/// <list type="bullet">
/// <item>true: FCS 校验通过 / FCS check passed</item>
/// <item>false: FCS 校验失败 / FCS check failed</item>
/// </list>
/// </remarks>
/// </returns>
public static bool? FcsCheckRight(byte[] content) public static bool? FcsCheckRight(byte[] content)
{ {
var fcsCheck = 0; var fcsCheck = 0;
// 确定起始位置 / Determine start position
// 如果首字节为 0x10从第 2 个字节开始 / If first byte is 0x10, start from 2nd byte
var start = content[0] == 0x10 ? 1 : 4; var start = content[0] == 0x10 ? 1 : 4;
// 特殊帧0xE5 直接返回 true / Special frame: 0xE5 returns true directly
if (content[0] == 0xE5) return true; if (content[0] == 0xE5) return true;
// 累加数据部分 / Sum data part
for (var i = start; i < content.Length - 2; i++) for (var i = start; i < content.Length - 2; i++)
fcsCheck += content[i]; fcsCheck += content[i];
// 取模 256 / Mod 256
fcsCheck = fcsCheck % 256; fcsCheck = fcsCheck % 256;
// 与 FCS 字节比较 / Compare with FCS byte
if (fcsCheck != content[content.Length - 2]) return false; if (fcsCheck != content[content.Length - 2]) return false;
return true; return true;
} }
} }

View File

@@ -1,38 +1,269 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 按长度断包的函数 /// 按长度断包辅助工具类 / Duplicate With Count Helper Utility Class
/// <remarks>
/// 提供根据指定位置计算数据包长度的功能,用于处理变长协议
/// Provides functionality to calculate packet length based on specified positions, used for variable-length protocols
/// <para>
/// 工作原理 / Working Principle:
/// <list type="number">
/// <item>从数据包的指定位置提取长度字节 / Extract length bytes from specified positions in packet</item>
/// <item>将多个长度字节组合成完整长度值 / Combine multiple length bytes into complete length value</item>
/// <item>加上固定头部长度 / Add fixed header length</item>
/// <item>返回完整数据包长度 / Return complete packet length</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>变长协议解析 / Variable-length protocol parsing</item>
/// <item>TCP 粘包处理 / TCP sticky packet handling</item>
/// <item>数据帧切分 / Data frame splitting</item>
/// <item>Modbus TCP (MBAP 头) / Modbus TCP (MBAP header)</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 示例 1: 长度字段在第 4-5 字节,头部固定 6 字节
/// // Example 1: Length field at bytes 4-5, fixed header 6 bytes
/// var lengthFunc = DuplicateWithCount.GetDuplcateFunc(
/// packageCountPositions: new List&lt;int&gt; { 4, 5 }, // 长度字节位置
/// otherCount: 6 // 固定头部长度
/// );
///
/// // 数据包 / Data packet:
/// // [00 01 00 00 00 0C] [数据...]
/// // │ │ │ │ │ │
/// // │ │ │ │ │ └─ 位置 5 (长度低字节)
/// // │ │ │ │ └─ 位置 4 (长度高字节)
/// // │ │ │ └─ 固定头部
/// // │ │ └─ 固定头部
/// // │ └─ 固定头部
/// // └─ 固定头部
///
/// int packetLength = lengthFunc(receivedData);
/// // packetLength = 12 (0x000C) + 6 = 18 字节
///
/// // 示例 2: 长度字段在第 2 字节,头部固定 3 字节
/// // Example 2: Length field at byte 2, fixed header 3 bytes
/// var simpleFunc = DuplicateWithCount.GetDuplcateFunc(
/// packageCountPositions: new List&lt;int&gt; { 2 },
/// otherCount: 3
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static class DuplicateWithCount public static class DuplicateWithCount
{ {
/// <summary> /// <summary>
/// 计算切分包的长度 /// 计算切分包的长度 / Calculate Split Packet Length
/// <remarks>
/// 内部方法,根据指定的位置信息计算数据包的完整长度
/// Internal method to calculate complete packet length based on specified position information
/// <para>
/// 计算过程 / Calculation Process:
/// <list type="number">
/// <item>遍历所有长度字节位置 / Iterate through all length byte positions</item>
/// <item>检查位置是否越界 / Check if position is out of bounds</item>
/// <item>将每个字节值累加ans = ans * 256 + byteValue / Accumulate each byte value: ans = ans * 256 + byteValue</item>
/// <item>加上固定长度totalLength = ans + otherCount / Add fixed length: totalLength = ans + otherCount</item>
/// <item>检查总长度是否超过接收数据长度 / Check if total length exceeds received data length</item>
/// </list>
/// </para>
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item><strong>&gt;= 0</strong> - 完整数据包长度 / Complete packet length</item>
/// <item><strong>-1</strong> - 数据不完整或位置越界 / Data incomplete or position out of bounds</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="receiveMessage">收到的报文信息</param> /// <param name="receiveMessage">
/// <param name="packageCountPositions">收到的断包长度查询位置</param> /// 收到的报文信息 / Received Message Information
/// <param name="otherCount">除指示的长度外其它位置的长度</param> /// <remarks>
/// <returns>切分后的报文信息</returns> /// 从设备接收到的原始字节数据
/// Raw byte data received from device
/// <para>
/// 数据结构 / Data Structure:
/// <list type="bullet">
/// <item>[长度字节 1][长度字节 2]...[数据部分] / [Length byte 1][Length byte 2]...[Data part]</item>
/// <item>长度字节位置由 packageCountPositions 指定 / Length byte positions specified by packageCountPositions</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="packageCountPositions">
/// 收到的断包长度查询位置 / Received Packet Length Query Positions
/// <remarks>
/// 长度字节在数据包中的位置数组
/// Array of length byte positions in packet
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>[4, 5] - 16 位长度字段,高字节在位置 4低字节在位置 5 / 16-bit length field, high byte at position 4, low byte at position 5</item>
/// <item>[2] - 8 位长度字段,在位置 2 / 8-bit length field at position 2</item>
/// <item>[4, 5, 6, 7] - 32 位长度字段 / 32-bit length field</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="otherCount">
/// 除指示的长度外其它位置的长度 / Length of Other Positions Except Indicated Length
/// <remarks>
/// 固定头部和尾部的总字节数
/// Total bytes of fixed header and tail
/// <para>
/// 包含内容 / Includes:
/// <list type="bullet">
/// <item>协议头部 / Protocol header</item>
/// <item>地址字段 / Address field</item>
/// <item>功能码 / Function code</item>
/// <item>校验码 / Checksum</item>
/// <item>结束符 / End marker</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>Modbus TCP MBAP 头6 字节 / Modbus TCP MBAP header: 6 bytes</item>
/// <item>简单协议3 字节 (起始符 + 地址 + 功能码) / Simple protocol: 3 bytes</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 切分后的报文信息长度 / Split Message Information Length
/// <remarks>
/// <list type="bullet">
/// <item>&gt;= 0: 完整数据包长度 / Complete packet length</item>
/// <item>-1: 数据不完整,需要继续接收 / -1: Data incomplete, need to continue receiving</item>
/// </list>
/// </remarks>
/// </returns>
private static int CalculateLength(byte[] receiveMessage, ICollection<int> packageCountPositions, int otherCount) private static int CalculateLength(byte[] receiveMessage, ICollection<int> packageCountPositions, int otherCount)
{ {
var ans = 0; var ans = 0;
// 遍历所有长度字节位置 / Iterate through all length byte positions
foreach (var position in packageCountPositions) foreach (var position in packageCountPositions)
{ {
if (position > receiveMessage.Length - 1) return -1; // 检查位置是否越界 / Check if position is out of bounds
if (position > receiveMessage.Length - 1)
{
return -1; // 位置越界,数据不完整 / Position out of bounds, data incomplete
}
// 累加长度字节值 / Accumulate length byte value
// 大端格式:高位在前 / Big-endian: high byte first
ans = ans * 256 + receiveMessage[position]; ans = ans * 256 + receiveMessage[position];
} }
if (ans + otherCount > receiveMessage.Length) return -1;
// 检查总长度是否超过接收数据长度 / Check if total length exceeds received data length
if (ans + otherCount > receiveMessage.Length)
{
return -1; // 数据不完整 / Data incomplete
}
// 返回完整长度 / Return complete length
return ans + otherCount; return ans + otherCount;
} }
/// <summary> /// <summary>
/// 获取长度函数 /// 获取长度计算函数 / Get Length Calculation Function
/// <remarks>
/// 工厂方法,返回一个用于计算数据包长度的委托函数
/// Factory method returning a delegate function for calculating packet length
/// <para>
/// 返回的函数用途 / Returned Function Purpose:
/// <list type="bullet">
/// <item>用于 Controller 的包长度计算 / Used for Controller's packet length calculation</item>
/// <item>用于数据帧切分 / Used for data frame splitting</item>
/// <item>用于 TCP 粘包处理 / Used for TCP sticky packet handling</item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>FifoController 的 lengthCalc 参数 / FifoController's lengthCalc parameter</item>
/// <item>MatchController 的 lengthCalc 参数 / MatchController's lengthCalc parameter</item>
/// <item>自定义控制器的长度计算 / Custom controller length calculation</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建长度计算函数 / Create length calculation function
/// var lengthCalc = DuplicateWithCount.GetDuplcateFunc(
/// packageCountPositions: new List&lt;int&gt; { 4, 5 },
/// otherCount: 6
/// );
///
/// // 用于 Controller / Use in Controller
/// var controller = new FifoController(
/// acquireTime: 10,
/// lengthCalc: lengthCalc, // 包长度计算函数
/// checkRightFunc: ContentCheck.Crc16CheckRight
/// );
///
/// // 计算数据包长度 / Calculate packet length
/// int packetLength = lengthCalc(receivedData);
///
/// // 如果返回 -1表示数据不完整需要继续接收
/// // If returns -1, data is incomplete, need to continue receiving
/// if (packetLength == -1)
/// {
/// // 继续接收数据 / Continue receiving data
/// }
/// else
/// {
/// // 数据完整,可以处理 / Data complete, can process
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="packageCountPositions">断包长度的位置信息</param> /// <param name="packageCountPositions">
/// <param name="otherCount">除指示的长度外其它位置的长度</param> /// 断包长度的位置信息 / Packet Length Position Information
/// <returns>断包函数</returns> /// <remarks>
/// 长度字节在数据包中的位置集合
/// Collection of length byte positions in packet
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>new List&lt;int&gt; { 4, 5 } - 16 位长度字段 / 16-bit length field</item>
/// <item>new List&lt;int&gt; { 2 } - 8 位长度字段 / 8-bit length field</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="otherCount">
/// 除指示的长度外其它位置的长度 / Length of Other Positions Except Indicated Length
/// <remarks>
/// 固定头部和尾部的总字节数
/// Total bytes of fixed header and tail
/// </remarks>
/// </param>
/// <returns>
/// 断包函数 / Packet Splitting Function
/// <remarks>
/// Func&lt;byte[], int&gt; 委托,输入字节数组,返回数据包长度
/// Func&lt;byte[], int&gt; delegate, input byte array, return packet length
/// <para>
/// 返回值 / Return Values:
/// <list type="bullet">
/// <item>&gt;= 0: 完整数据包长度 / Complete packet length</item>
/// <item>-1: 数据不完整 / Data incomplete</item>
/// </list>
/// </para>
/// </remarks>
/// </returns>
public static Func<byte[], int> GetDuplcateFunc(ICollection<int> packageCountPositions, int otherCount) public static Func<byte[], int> GetDuplcateFunc(ICollection<int> packageCountPositions, int otherCount)
{ {
return receiveMessage => CalculateLength(receiveMessage, packageCountPositions, otherCount); return receiveMessage => CalculateLength(receiveMessage, packageCountPositions, otherCount);

View File

@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -6,28 +6,71 @@ using System.Threading;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 先入先出式控制器 /// 先入先出式控制器 / First-In-First-Out (FIFO) Controller
/// <remarks>
/// 按照消息添加的顺序依次发送消息,确保消息的有序性
/// Send messages in the order they are added, ensuring message order
/// <para>
/// 适用场景 / Use Cases:
/// <list type="bullet">
/// <item>需要严格按顺序通信的场景 / Scenarios requiring strict order communication</item>
/// <item>Modbus RTU 串口通信 / Modbus RTU Serial Communication</item>
/// <item>单主站多从站系统 / Single Master Multi-Slave System</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class FifoController : BaseController public class FifoController : BaseController
{ {
private static readonly ILogger<FifoController> logger = LogProvider.CreateLogger<FifoController>(); private static readonly ILogger<FifoController> logger = LogProvider.CreateLogger<FifoController>();
private MessageWaitingDef _currentSendingPos; private MessageWaitingDef _currentSendingPos;
private int _waitingListMaxCount; private int _waitingListMaxCount;
/// <summary> /// <summary>
/// 间隔时间 /// 间隔时间 (毫秒) / Interval Time (milliseconds)
/// <remarks>
/// 两次发送之间的最小时间间隔
/// Minimum time interval between two sends
/// </remarks>
/// </summary> /// </summary>
public int AcquireTime { get; } public int AcquireTime { get; }
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 初始化 FIFO 控制器,设置时间间隔和队列长度
/// Initialize FIFO controller, setting time interval and queue length
/// </remarks>
/// </summary> /// </summary>
/// <param name="acquireTime">间隔时间</param> /// <param name="acquireTime">
/// <param name="lengthCalc">包切分长度函数</param> /// 间隔时间 (毫秒) / Interval time (milliseconds)
/// <param name="checkRightFunc">包校验函数</param> /// <remarks>
/// <param name="waitingListMaxCount">包等待队列长度</param> /// 0 表示无延迟,>0 表示延迟毫秒数
/// 0 means no delay, >0 means delay in milliseconds
/// </remarks>
/// </param>
/// <param name="lengthCalc">
/// 包切分长度函数 / Packet split length function
/// <remarks>
/// 用于计算消息包的长度
/// Used to calculate message packet length
/// </remarks>
/// </param>
/// <param name="checkRightFunc">
/// 包校验函数 / Packet check function
/// <remarks>
/// 用于校验接收到的消息是否正确
/// Used to verify if received message is correct
/// </remarks>
/// </param>
/// <param name="waitingListMaxCount">
/// 包等待队列长度 / Packet waiting queue length
/// <remarks>
/// 最大等待消息数量,超过此数量将拒绝新消息
/// Maximum waiting message count, will reject new messages if exceeded
/// </remarks>
/// </param>
public FifoController(int acquireTime, Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) public FifoController(int acquireTime, Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null)
: base(lengthCalc, checkRightFunc) : base(lengthCalc, checkRightFunc)
{ {
@@ -36,6 +79,14 @@ namespace Modbus.Net
} }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>
/// 发送消息控制内部逻辑 / Sending Message Control Inner Logic
/// <remarks>
/// 按 FIFO 顺序处理等待队列中的消息
/// Process messages in waiting queue in FIFO order
/// </remarks>
/// </summary>
/// <param name="token">取消令牌 / Cancellation token</param>
protected override void SendingMessageControlInner(CancellationToken token) protected override void SendingMessageControlInner(CancellationToken token)
{ {
while (true) while (true)
@@ -58,65 +109,3 @@ namespace Modbus.Net
} }
else else
{ {
if (WaitingMessages.Count <= 0)
{
_currentSendingPos = null;
}
else if (WaitingMessages.IndexOf(_currentSendingPos) == -1)
{
_currentSendingPos = WaitingMessages.First();
_currentSendingPos.SendMutex.Set();
}
}
}
catch (ObjectDisposedException e)
{
logger.LogError(e, "Controller _currentSendingPos disposed");
_currentSendingPos = null;
}
catch (Exception e)
{
logger.LogError(e, "Controller throws exception");
SendStop();
}
}
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}
/// <inheritdoc />
protected override (string, string)? GetKeyFromMessage(byte[] message)
{
return null;
}
/// <inheritdoc />
protected override MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage)
{
MessageWaitingDef ans;
lock (WaitingMessages)
{
ans = WaitingMessages.FirstOrDefault();
}
return ans;
}
/// <inheritdoc />
protected override bool AddMessageToList(MessageWaitingDef def)
{
if (WaitingMessages.Count > _waitingListMaxCount)
{
return false;
}
if (!IsSending)
{
return false;
}
var success = base.AddMessageToList(def);
return success;
}
}
}

View File

@@ -1,61 +1,267 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 通讯号匹配模式的控制器 /// 匹配控制器 / Match Controller
/// <remarks>
/// 基于消息内容匹配的控制器,根据响应内容的特定字节位置匹配对应的请求
/// Controller based on message content matching, matches corresponding request based on specific byte positions in response
/// <para>
/// 工作原理 / Working Principle:
/// <list type="number">
/// <item>从请求消息中提取关键字节位置 / Extract key byte positions from request message</item>
/// <item>生成匹配键 (Key) / Generate match key</item>
/// <item>收到响应时,提取相同位置的字节 / When receiving response, extract bytes at same positions</item>
/// <item>根据键匹配对应的请求 / Match corresponding request based on key</item>
/// <item>完成匹配的消息 / Complete matched message</item>
/// </list>
/// </para>
/// <para>
/// 适用场景 / Use Cases:
/// <list type="bullet">
/// <item>多从站并发通信 / Multi-slave concurrent communication</item>
/// <item>响应顺序不固定的场景 / Scenarios where response order is not fixed</item>
/// <item>需要精确匹配请求响应的场合 / Situations requiring precise request-response matching</item>
/// </list>
/// </para>
/// <para>
/// 与 FIFO 控制器的区别 / Difference from FIFO Controller:
/// <list type="bullet">
/// <item>FIFO: 严格按顺序匹配 / FIFO: Strictly match in order</item>
/// <item>Match: 按内容匹配,支持乱序响应 / Match: Match by content, supports out-of-order responses</item>
/// </list>
/// </para>
/// <para>
/// 匹配键示例 / Match Key Example:
/// <code>
/// // Modbus 请求帧 / Modbus request frame
/// Request: [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B]
/// │ │
/// │ └─ 功能码 (位置 1)
/// └─ 从站地址 (位置 0)
///
/// // 匹配位置:[(0, 0), (1, 1)]
/// // 生成的键:"1 3 " (从站地址 1, 功能码 3)
///
/// // Modbus 响应帧 / Modbus response frame
/// Response: [0x01, 0x03, 0x14, ...数据..., CRC]
/// │ │
/// │ └─ 功能码 (位置 1)
/// └─ 从站地址 (位置 0)
///
/// // 匹配到相同的键 "1 3 ",完成匹配
/// // Match same key "1 3 ", complete matching
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class MatchController : FifoController public class MatchController : FifoController
{ {
/// <summary> /// <summary>
/// 匹配字典 /// 匹配字典数组 / Match Dictionary Array
/// <remarks>
/// 每个 Collection 代表一个匹配集合,包含需要匹配的字节位置对
/// Each Collection represents a match set, containing byte position pairs to match
/// <para>
/// 数据结构 / Data Structure:
/// <list type="bullet">
/// <item>外层数组:多个匹配规则 / Outer array: multiple match rules</item>
/// <item>Collection: 一个规则中的所有位置对 / Collection: all position pairs in one rule</item>
/// <item>(int, int): 位置对Item1=请求位置Item2=响应位置 / (int, int): position pair, Item1=request position, Item2=response position</item>
/// </list>
/// </para>
/// <para>
/// 匹配计算 / Match Calculation:
/// <list type="bullet">
/// <item>遍历每个匹配集合 / Iterate through each match set</item>
/// <item>提取指定位置的字节值 / Extract byte values at specified positions</item>
/// <item>按顺序拼接为字符串 / Concatenate to string in order</item>
/// <item>多个集合的结果用空格分隔 / Separate results of multiple sets with space</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // 匹配规则:提取从站地址和功能码
/// // Match rule: extract slave address and function code
/// KeyMatches = new ICollection&lt;(int, int)&gt;[]
/// {
/// new List&lt;(int, int)&gt; { (0, 0), (1, 1) } // 位置 0 和 1
/// };
///
/// // 请求:[0x01, 0x03, ...]
/// // 键:"1 3 " (0x01=1, 0x03=3)
///
/// // 响应:[0x01, 0x03, ...]
/// // 键:"1 3 " (匹配成功)
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
protected ICollection<(int, int)>[] KeyMatches { get; } protected ICollection<(int, int)>[] KeyMatches { get; }
/// <summary> /// <summary>
/// 构造 /// 构造函数 / Constructor
/// <remarks>
/// 初始化匹配控制器,设置匹配规则和超时时间
/// Initialize Match Controller, setting match rules and timeout
/// </remarks>
/// </summary> /// </summary>
/// <param name="keyMatches">匹配字典每个Collection代表一个匹配集合每一个匹配集合中的数字代表需要匹配的位置最后计算出来的数字是所有位置数字按照集合排序后叠放在一起</param> /// <param name="keyMatches">
/// <param name="acquireTime">获取间隔</param> /// 匹配字典 / Match dictionary
/// <param name="lengthCalc">包长度计算</param> /// <remarks>
/// <param name="checkRightFunc">包校验函数</param> /// 每个 Collection 代表一个匹配集合,每一个匹配集合中的数字代表需要匹配的位置
/// <param name="waitingListMaxCount">包等待队列长度</param> /// Each Collection represents a match set, numbers in each set represent positions to match
/// <para>
/// 计算规则 / Calculation Rules:
/// <list type="bullet">
/// <item>提取每个位置的字节值 / Extract byte value at each position</item>
/// <item>按集合顺序叠放在一起 / Stack together in set order</item>
/// <item>转换为字符串作为键 / Convert to string as key</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="acquireTime">
/// 获取间隔 (毫秒) / Get interval (milliseconds)
/// <remarks>两次发送之间的最小时间间隔 / Minimum time interval between two sends</remarks>
/// </param>
/// <param name="lengthCalc">
/// 包长度计算函数 / Packet length calculation function
/// <remarks>用于计算消息包的长度 / Used to calculate message packet length</remarks>
/// </param>
/// <param name="checkRightFunc">
/// 包校验函数 / Packet check function
/// <remarks>用于校验接收到的消息是否正确 / Used to verify if received message is correct</remarks>
/// </param>
/// <param name="waitingListMaxCount">
/// 包等待队列长度 / Packet waiting queue length
/// <remarks>最大等待消息数量 / Maximum waiting message count</remarks>
/// </param>
public MatchController(ICollection<(int, int)>[] keyMatches, int acquireTime, public MatchController(ICollection<(int, int)>[] keyMatches, int acquireTime,
Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(acquireTime, lengthCalc, checkRightFunc, waitingListMaxCount) Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(acquireTime, lengthCalc, checkRightFunc, waitingListMaxCount)
{ {
KeyMatches = keyMatches; KeyMatches = keyMatches;
} }
/// <inheritdoc /> /// <summary>
/// 从消息中提取匹配键 / Extract Match Key from Message
/// <remarks>
/// 根据 KeyMatches 定义的位置,从消息中提取字节并生成匹配键
/// Extract bytes from message based on KeyMatches positions and generate match key
/// <para>
/// 提取过程 / Extraction Process:
/// <list type="number">
/// <item>遍历每个匹配集合 / Iterate through each match set</item>
/// <item>对每个位置对,提取字节值 / For each position pair, extract byte value</item>
/// <item>累加计算tmpCount = tmpCount * 256 + byteValue / Accumulate: tmpCount = tmpCount * 256 + byteValue</item>
/// <item>将结果转换为字符串 / Convert result to string</item>
/// <item>多个集合用空格连接 / Join multiple sets with space</item>
/// </list>
/// </para>
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item>Item1: 请求消息的键 / Request message key</item>
/// <item>Item2: 响应消息的键 / Response message key</item>
/// <item>通常两者相同 / Usually both are the same</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="message">
/// 消息内容 / Message content
/// <remarks>待提取键的字节数组 / Byte array to extract key from</remarks>
/// </param>
/// <returns>
/// 匹配键元组 / Match key tuple
/// <remarks>
/// (请求键,响应键) / (Request key, Response key)
/// null: 提取失败 (位置超出范围) / null: extraction failed (position out of range)
/// </remarks>
/// </returns>
protected override (string, string)? GetKeyFromMessage(byte[] message) protected override (string, string)? GetKeyFromMessage(byte[] message)
{ {
string ans1 = ""; string ans1 = ""; // 请求键 / Request key
string ans2 = ""; string ans2 = ""; // 响应键 / Response key
// 遍历每个匹配集合 / Iterate through each match set
foreach (var matchPoses in KeyMatches) foreach (var matchPoses in KeyMatches)
{ {
int tmpCount = 0, tmpCount2 = 0; int tmpCount = 0, tmpCount2 = 0;
// 处理每个位置对 / Process each position pair
foreach (var matchPos in matchPoses) foreach (var matchPos in matchPoses)
{ {
if (matchPos.Item1 > message.Length - 1 || matchPos.Item2 > message.Length - 1) return null; // 检查位置是否越界 / Check if position is out of bounds
if (matchPos.Item1 > message.Length - 1 || matchPos.Item2 > message.Length - 1)
{
return null; // 位置越界,返回 null / Position out of bounds, return null
}
// 累加计算键值 / Accumulate key value
tmpCount = tmpCount * 256 + message[matchPos.Item1]; tmpCount = tmpCount * 256 + message[matchPos.Item1];
tmpCount2 = tmpCount2 * 256 + message[matchPos.Item2]; tmpCount2 = tmpCount2 * 256 + message[matchPos.Item2];
} }
// 添加到结果字符串 / Add to result string
ans1 += tmpCount + " "; ans1 += tmpCount + " ";
ans2 += tmpCount2 + " "; ans2 += tmpCount2 + " ";
} }
return (ans1, ans2); return (ans1, ans2);
} }
/// <inheritdoc /> /// <summary>
/// 从等待队列中匹配消息 / Match Message from Waiting Queue
/// <remarks>
/// 根据接收到的响应消息,从等待队列中查找匹配的请求
/// Find matching request from waiting queue based on received response message
/// <para>
/// 匹配流程 / Matching Flow:
/// <list type="number">
/// <item>从响应消息提取键 / Extract key from response message</item>
/// <item>在等待队列中查找相同键的请求 / Find request with same key in waiting queue</item>
/// <item>返回匹配的消息定义 / Return matched message definition</item>
/// <item>如果没有匹配,返回 null / Return null if no match</item>
/// </list>
/// </para>
/// <para>
/// 与 FIFO 的区别 / Difference from FIFO:
/// <list type="bullet">
/// <item>FIFO: 返回队列第一个 / FIFO: return first in queue</item>
/// <item>Match: 按内容匹配 / Match: match by content</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="receiveMessage">
/// 接收到的响应消息 / Received response message
/// <remarks>设备返回的字节数组 / Byte array returned from device</remarks>
/// </param>
/// <returns>
/// 匹配的消息等待定义 / Matched message waiting definition
/// <remarks>
/// null: 未找到匹配 / null: no match found
/// </remarks>
/// </returns>
protected override MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage) protected override MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage)
{ {
if (receiveMessage == null) return null; if (receiveMessage == null)
{
return null;
}
// 从响应提取键 / Extract key from response
var returnKey = GetKeyFromMessage(receiveMessage); var returnKey = GetKeyFromMessage(receiveMessage);
MessageWaitingDef ans; MessageWaitingDef ans;
lock (WaitingMessages) lock (WaitingMessages)
{ {
// 查找匹配键的请求 / Find request with matching key
ans = WaitingMessages.FirstOrDefault(p => returnKey.HasValue && p.Key == returnKey.Value.Item2); ans = WaitingMessages.FirstOrDefault(p => returnKey.HasValue && p.Key == returnKey.Value.Item2);
} }
return ans; return ans;

View File

@@ -0,0 +1,489 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Modbus.Net
{
/// <summary>
/// 无响应控制器 / No Response Controller
/// <remarks>
/// 用于不需要等待响应的通信场景,消息发送后立即标记为完成
/// Used for communication scenarios that don't require waiting for response, messages are marked as complete immediately after sending
/// <para>
/// 适用场景 / Use Cases:
/// <list type="bullet">
/// <item>广播消息 / Broadcast messages</item>
/// <item>写入命令 (不需要确认) / Write commands (no acknowledgment required)</item>
/// <item>单向通信 / One-way communication</item>
/// <item>Modbus 写单寄存器/写多寄存器 (某些设备不返回响应) / Modbus Write Single/Multiple Register (some devices don't return response)</item>
/// </list>
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>不等待设备响应 / Does not wait for device response</item>
/// <item>发送后立即返回空响应 / Returns empty response immediately after sending</item>
/// <item>适合高速写入场景 / Suitable for high-speed write scenarios</item>
/// <item>无法检测通信错误 / Cannot detect communication errors</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 配置无响应控制器 / Configure No Response Controller
/// var controller = new NoResponseController(acquireTime: 10); // 10ms 间隔
/// connector.AddController(controller);
///
/// // 发送写入命令 (不等待响应) / Send write command (don't wait for response)
/// await utility.SetDatasAsync("4X 1", new object[] { (ushort)100 });
/// // 立即返回,不等待设备确认
/// // Returns immediately, doesn't wait for device acknowledgment
/// </code>
/// </para>
/// </remarks>
/// </summary>
public class NoResponseController : IController
{
private static readonly ILogger<NoResponseController> logger = LogProvider.CreateLogger<NoResponseController>();
/// <summary>
/// 等待发送的消息队列 / Waiting Message Queue
/// <remarks>
/// 存储待发送的消息定义
/// Stores pending message definitions
/// <para>
/// 线程安全:使用 lock 保护 / Thread-safe: protected with lock
/// </para>
/// </remarks>
/// </summary>
protected List<MessageWaitingDef> WaitingMessages { get; set; }
/// <summary>
/// 消息发送线程 / Message Sending Thread
/// <remarks>
/// 后台线程,负责按顺序发送消息
/// Background thread responsible for sending messages in order
/// </remarks>
/// </summary>
protected Task SendingThread { get; set; }
/// <summary>
/// 发送间隔时间 (毫秒) / Send Interval Time (milliseconds)
/// <remarks>
/// 两次发送之间的最小时间间隔
/// Minimum time interval between two sends
/// <para>
/// 设置建议 / Recommendations:
/// <list type="bullet">
/// <item>Modbus RTU: 10-50ms (取决于波特率) / Modbus RTU: 10-50ms (depends on baud rate)</item>
/// <item>Modbus TCP: 0-10ms / Modbus TCP: 0-10ms</item>
/// <item>高速设备0ms / High-speed devices: 0ms</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public int AcquireTime { get; }
/// <summary>
/// 消息发送线程是否在运行 / Whether Message Sending Thread is Running
/// <remarks>
/// true: 线程正在运行 / Thread is running
/// false: 线程已停止 / Thread is stopped
/// </remarks>
/// </summary>
public virtual bool IsSending => SendingThread != null;
private MessageWaitingDef _currentSendingPos;
private CancellationTokenSource _sendingThreadCancel;
/// <summary>
/// 构造函数 / Constructor
/// <remarks>
/// 初始化无响应控制器
/// Initialize No Response Controller
/// </remarks>
/// </summary>
/// <param name="acquireTime">
/// 发送间隔时间 (毫秒) / Send interval time (milliseconds)
/// <remarks>
/// 0: 无延迟,立即发送 / 0: No delay, send immediately
/// >0: 延迟指定毫秒数 / >0: Delay specified milliseconds
/// </remarks>
/// </param>
public NoResponseController(int acquireTime)
{
WaitingMessages = new List<MessageWaitingDef>();
AcquireTime = acquireTime;
}
/// <summary>
/// 添加消息到发送队列 / Add Message to Send Queue
/// <remarks>
/// 将待发送的消息添加到队列,并启动发送线程 (如果未运行)
/// Add pending message to queue, and start sending thread if not running
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>创建消息等待定义 / Create message waiting definition</item>
/// <item>添加到队列 / Add to queue</item>
/// <item>启动发送线程 (如果需要) / Start sending thread (if needed)</item>
/// <item>返回消息定义 / Return message definition</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="sendMessage">
/// 待发送的消息 / Message to send
/// <remarks>通常是字节数组 / Usually byte array</remarks>
/// </param>
/// <returns>
/// 消息等待定义 / Message waiting definition
/// <remarks>
/// null: 添加失败 (队列已满或重复) / null: Add failed (queue full or duplicate)
/// </remarks>
/// </returns>
public MessageWaitingDef AddMessage(byte[] sendMessage)
{
var def = new MessageWaitingDef
{
Key = GetKeyFromMessage(sendMessage)?.Item1,
SendMessage = sendMessage,
SendMutex = new AutoResetEvent(false),
ReceiveMutex = new AutoResetEvent(false)
};
if (AddMessageToList(def))
{
return def;
}
return null;
}
/// <summary>
/// 发送消息的内部实现 / Internal Implementation of Message Sending
/// <remarks>
/// 后台线程方法,按 FIFO 顺序处理消息队列
/// Background thread method, processes message queue in FIFO order
/// <para>
/// 处理流程 / Processing Flow:
/// <list type="number">
/// <item>等待间隔时间 / Wait for interval time</item>
/// <item>从队列获取第一个消息 / Get first message from queue</item>
/// <item>触发发送信号 / Trigger send signal</item>
/// <item>立即返回空响应 (不等待设备) / Return empty response immediately (don't wait for device)</item>
/// <item>从队列移除消息 / Remove message from queue</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="token">取消令牌 / Cancellation Token</param>
protected void SendingMessageControlInner(CancellationToken token)
{
while (true)
{
// 等待间隔时间 / Wait for interval time
if (AcquireTime > 0)
{
Thread.Sleep(AcquireTime);
}
lock (WaitingMessages)
{
try
{
if (_currentSendingPos == null)
{
// 队列为空时,获取第一个消息 / When queue is empty, get first message
if (WaitingMessages.Count > 0)
{
_currentSendingPos = WaitingMessages.First();
_currentSendingPos.SendMutex.Set(); // 触发发送 / Trigger send
_currentSendingPos.ReceiveMessage = new byte[0]; // 空响应 / Empty response
_currentSendingPos.ReceiveMutex.Set(); // 立即完成 / Complete immediately
ForceRemoveWaitingMessage(_currentSendingPos); // 移除消息 / Remove message
}
}
else
{
// 当前消息正在处理 / Current message is being processed
if (WaitingMessages.Count <= 0)
{
// 队列为空,重置当前位置 / Queue is empty, reset current position
_currentSendingPos = null;
}
else if (WaitingMessages.IndexOf(_currentSendingPos) == -1)
{
// 当前消息已不在队列,处理下一个 / Current message not in queue, process next
_currentSendingPos = WaitingMessages.First();
_currentSendingPos.SendMutex.Set();
_currentSendingPos.ReceiveMessage = new byte[0];
_currentSendingPos.ReceiveMutex.Set();
ForceRemoveWaitingMessage(_currentSendingPos);
}
}
}
catch (ObjectDisposedException e)
{
logger.LogError(e, "Controller _currentSendingPos disposed");
_currentSendingPos = null;
}
catch (Exception e)
{
logger.LogError(e, "Controller throws exception");
SendStop();
}
}
// 检查取消请求 / Check cancellation request
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}
/// <summary>
/// 停止发送线程 / Stop Sending Thread
/// <remarks>
/// 取消并清理发送线程
/// Cancel and cleanup sending thread
/// <para>
/// 清理步骤 / Cleanup Steps:
/// <list type="number">
/// <item>清空队列 / Clear queue</item>
/// <item>取消令牌 / Cancel token</item>
/// <item>等待线程结束 / Wait for thread to end</item>
/// <item>释放线程资源 / Release thread resources</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
public virtual void SendStop()
{
Clear();
_sendingThreadCancel?.Cancel();
if (SendingThread != null)
{
while (!SendingThread.IsCanceled)
{
Thread.Sleep(10);
}
SendingThread.Dispose();
SendingThread = null;
}
Clear();
}
/// <summary>
/// 启动发送线程 / Start Sending Thread
/// <remarks>
/// 启动后台发送线程 (如果未运行)
/// Start background sending thread if not running
/// <para>
/// 线程安全:使用 IsSending 检查 / Thread-safe: check with IsSending
/// </para>
/// </remarks>
/// </summary>
public virtual async void SendStart()
{
if (!IsSending)
{
_sendingThreadCancel = new CancellationTokenSource();
SendingThread = Task.Run(() => SendingMessageControlInner(_sendingThreadCancel.Token), _sendingThreadCancel.Token);
try
{
await SendingThread;
}
catch (OperationCanceledException)
{ /* 正常取消,忽略 / Normal cancellation, ignore */ }
finally
{
_sendingThreadCancel.Dispose();
_sendingThreadCancel = null;
}
}
}
/// <summary>
/// 清空消息队列 / Clear Message Queue
/// <remarks>
/// 移除所有等待发送的消息
/// Remove all pending messages
/// <para>
/// 线程安全:使用 lock 保护 / Thread-safe: protected with lock
/// </para>
/// </remarks>
/// </summary>
public void Clear()
{
if (WaitingMessages != null)
{
lock (WaitingMessages)
{
WaitingMessages.Clear();
}
}
}
/// <summary>
/// 将消息添加到队列 / Add Message to Queue
/// <remarks>
/// 线程安全地将消息添加到等待队列
/// Thread-safely add message to waiting queue
/// <para>
/// 去重策略 / Deduplication Strategy:
/// <list type="bullet">
/// <item>如果 Key 已存在,拒绝添加 / If Key exists, reject add</item>
/// <item>Key 为 null 时允许添加 / Allow add when Key is null</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="def">
/// 消息等待定义 / Message waiting definition
/// <remarks>包含发送消息和同步对象 / Contains send message and sync objects</remarks>
/// </param>
/// <returns>
/// 是否添加成功 / Whether add is successful
/// <remarks>
/// true: 添加成功 / Add successful
/// false: 添加失败 (重复) / Add failed (duplicate)
/// </remarks>
/// </returns>
protected virtual bool AddMessageToList(MessageWaitingDef def)
{
var ans = false;
lock (WaitingMessages)
{
// 检查是否重复 / Check for duplicate
if (WaitingMessages.FirstOrDefault(p => p.Key == def.Key) == null || def.Key == null)
{
WaitingMessages.Add(def);
ans = true;
}
}
return ans;
}
/// <summary>
/// 从消息中提取检索关键字 / Extract Retrieval Key from Message
/// <remarks>
/// 用于消息去重和匹配
/// Used for message deduplication and matching
/// <para>
/// 默认实现:返回 null (不使用关键字)
/// Default implementation: returns null (no key used)
/// </para>
/// <para>
/// 派生类可以重写此方法以实现自定义关键字提取
/// Derived classes can override this method for custom key extraction
/// </para>
/// </remarks>
/// </summary>
/// <param name="message">
/// 消息内容 / Message content
/// <remarks>待发送的字节数组 / Byte array to send</remarks>
/// </param>
/// <returns>
/// 消息的检索关键字 / Message retrieval key
/// <remarks>(Key, SubKey) 元组null 表示不使用关键字 / (Key, SubKey) tuple, null means no key used</remarks>
/// </returns>
protected (string, string)? GetKeyFromMessage(byte[] message)
{
return null;
}
/// <summary>
/// 确认消息 (无响应控制器版本) / Confirm Message (No Response Controller version)
/// <remarks>
/// 对于无响应控制器,直接返回接收到的消息
/// For No Response Controller, directly return received message
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item>包含接收消息和 true 标志的列表 / List containing received message and true flag</item>
/// <item>true 表示消息已确认 / true indicates message is confirmed</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="receiveMessage">
/// 接收到的消息 / Received message
/// <remarks>设备返回的数据 / Data returned from device</remarks>
/// </param>
/// <returns>
/// 确认结果列表 / Confirmation result list
/// <remarks>每个元素为 (消息,是否确认) 元组 / Each element is (message, is confirmed) tuple</remarks>
/// </returns>
public ICollection<(byte[], bool)> ConfirmMessage(byte[] receiveMessage)
{
var ans = new List<(byte[], bool)>
{
(receiveMessage, true)
};
return ans;
}
/// <summary>
/// 从等待队列中获取匹配的消息 / Get Matching Message from Waiting Queue
/// <remarks>
/// 根据接收到的消息,从等待队列中查找匹配的消息定义
/// Find matching message definition from waiting queue based on received message
/// <para>
/// 匹配策略 / Matching Strategy:
/// <list type="bullet">
/// <item>FIFO: 返回队列第一个消息 / FIFO: return first message in queue</item>
/// <item>无响应控制器不使用匹配 / No Response Controller doesn't use matching</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="receiveMessage">
/// 接收到的消息 / Received message
/// <remarks>设备返回的数据 / Data returned from device</remarks>
/// </param>
/// <returns>
/// 匹配的消息等待定义 / Matching message waiting definition
/// <remarks>null: 未找到匹配 / null: no match found</remarks>
/// </returns>
protected MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage)
{
MessageWaitingDef ans;
lock (WaitingMessages)
{
ans = WaitingMessages.FirstOrDefault();
}
return ans;
}
/// <summary>
/// 强制移除等待消息 / Force Remove Waiting Message
/// <remarks>
/// 从等待队列中移除指定的消息定义
/// Remove specified message definition from waiting queue
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>消息发送完成后 / After message is sent</item>
/// <item>消息超时后 / After message timeout</item>
/// <item>消息取消后 / After message cancellation</item>
/// </list>
/// </para>
/// </remarks>
/// </summary>
/// <param name="def">
/// 要移除的消息定义 / Message definition to remove
/// <remarks>消息等待定义对象 / Message waiting definition object</remarks>
/// </param>
public void ForceRemoveWaitingMessage(MessageWaitingDef def)
{
lock (WaitingMessages)
{
if (WaitingMessages.IndexOf(def) >= 0)
{
WaitingMessages.Remove(def);
}
}
}
}
}

View File

@@ -1,152 +1,237 @@
using System;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 端格式 /// 端格式枚举 / Endianness Enum
/// <remarks>
/// 定义多字节数据的字节顺序和位顺序
/// Defines byte order and bit order for multi-byte data
/// <para>
/// 字节序说明 / Byte Order Description:
/// <list type="bullet">
/// <item><strong>Little Endian (小端)</strong> - 低字节在前,高字节在后 / Low byte first, high byte last</item>
/// <item><strong>Big Endian (大端)</strong> - 高字节在前,低字节在后 / High byte first, low byte last</item>
/// </list>
/// </para>
/// <para>
/// 位序说明 / Bit Order Description:
/// <list type="bullet">
/// <item><strong>LSB (Least Significant Bit)</strong> - 最低有效位在前 / Lowest bit first</item>
/// <item><strong>MSB (Most Significant Bit)</strong> - 最高有效位在前 / Highest bit first</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example (16 位值 0x1234):
/// <list type="bullet">
/// <item>Little Endian: [0x34, 0x12]</item>
/// <item>Big Endian: [0x12, 0x34]</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public partial class Endian public enum Endian
{ {
/// <summary> /// <summary>
/// 小端 /// 小端格式 (Little Endian, LSB) / Little Endian Format
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>低字节存储在低地址 / Low byte stored at low address</item>
/// <item>低有效位在前 / Least significant bit first</item>
/// <item>x86/x64 架构使用 / Used by x86/x64 architecture</item>
/// </list>
/// </para>
/// <para>
/// 示例 (32 位整数 0x12345678) / Example (32-bit integer 0x12345678):
/// <code>内存布局 / Memory Layout: [0x78, 0x56, 0x34, 0x12]</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public const int LittleEndianLsb = 1; LittleEndianLsb,
/// <summary> /// <summary>
/// 大端-大端位 /// 大端格式 (Big Endian, LSB) / Big Endian Format
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>高字节存储在低地址 / High byte stored at low address</item>
/// <item>低有效位在前 / Least significant bit first</item>
/// <item>网络字节序 / Network byte order</item>
/// <item>Modbus TCP 协议使用 / Used by Modbus TCP protocol</item>
/// </list>
/// </para>
/// <para>
/// 示例 (32 位整数 0x12345678) / Example (32-bit integer 0x12345678):
/// <code>内存布局 / Memory Layout: [0x12, 0x34, 0x56, 0x78]</code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public const int BigEndianLsb = 2; BigEndianLsb,
/// <summary> /// <summary>
/// 大端-大端位 /// 大端格式 + 位反转 (Big Endian, MSB) / Big Endian Format with Bit Reversal
/// <remarks>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>高字节存储在低地址 / High byte stored at low address</item>
/// <item>高有效位在前 / Most significant bit first</item>
/// <item>某些特殊设备使用 / Used by some special devices</item>
/// <item>位操作时需要反转 / Bit reversal needed for bit operations</item>
/// </list>
/// </para>
/// <para>
/// 位反转示例 (字节 0b00000001) / Bit Reversal Example (byte 0b00000001):
/// <code>反转后 / After reversal: 0b10000000</code>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>某些工业协议 / Some industrial protocols</item>
/// <item>特殊硬件设备 / Special hardware devices</item>
/// <item>需要 MSB 位序的场合 / Situations requiring MSB bit order</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public const int BigEndianMsb = 3; BigEndianMsb
#pragma warning disable
protected int Value { get; set; }
protected Endian(int value)
{
Value = value;
}
public static implicit operator int(Endian endian)
{
return endian.Value;
}
public static implicit operator Endian(int value)
{
return new Endian(value);
}
public static implicit operator Endian(string value)
{
return Endian.Parse(value);
}
public static implicit operator string(Endian endian)
{
return endian.ToString();
}
public static Endian Parse(string value)
{
var Assemblies = AssemblyHelper.GetAllLibraryAssemblies();
foreach (var assembly in Assemblies)
{
if (assembly.GetType("Modbus.Net.Endian")?.GetField(value) != null)
{
return (int)assembly.GetType("Modbus.Net.Endian").GetField(value).GetValue(null);
}
}
throw new NotSupportedException("Endian name " + value + " is not supported.");
}
public override string ToString()
{
var Assemblies = AssemblyHelper.GetAllLibraryAssemblies();
foreach (var assembly in Assemblies)
{
var endianType = assembly.GetType("Modbus.Net.Endian");
if (endianType != null)
{
foreach (var field in endianType.GetFields())
{
if ((int)field.GetValue(null) == Value)
{
return field.Name;
}
}
}
}
return null;
}
#pragma warning restore
} }
/// <summary> /// <summary>
/// 读写设备值的方式 /// 机器数据类型枚举 / Machine Data Type Enum
/// <remarks>
/// 定义 Machine 层 API 中数据的键类型
/// Defines key type for data in Machine layer API
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>GetDatas 方法指定返回数据的索引方式 / Specify indexing method for GetDatas method return data</item>
/// <item>SetDatas 方法指定写入数据的索引方式 / Specify indexing method for SetDatas method write data</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public enum MachineDataType public enum MachineDataType
{ {
/// <summary> /// <summary>
/// 地址 /// 地址索引 / Index by Address
/// <remarks>
/// <para>
/// 使用 AddressUnit 的 Address 属性作为键
/// Use AddressUnit's Address property as key
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // 获取地址为 1 的数据 / Get data with address 1
/// var result = machine.GetDatas(MachineDataType.Address);
/// var value = result["1"].DeviceValue;
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
Address, Address,
/// <summary> /// <summary>
/// 通讯标识 /// 按通信标签索引 / Index by Communication Tag
/// <remarks>
/// <para>
/// 使用 AddressUnit 的 CommunicationTag 属性作为键
/// Use AddressUnit's CommunicationTag property as key
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>更语义化的标识符 / More semantic identifier</item>
/// <item>适合业务逻辑使用 / Suitable for business logic</item>
/// <item>推荐使用 / Recommended</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // 定义地址时设置通信标签 / Set communication tag when defining address
/// new AddressUnit() {
/// CommunicationTag = "Temperature",
/// Address = 1,
/// ...
/// }
///
/// // 通过标签获取数据 / Get data by tag
/// var result = machine.GetDatas(MachineDataType.CommunicationTag);
/// var temperature = result["Temperature"].DeviceValue;
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
CommunicationTag, CommunicationTag,
/// <summary> /// <summary>
/// 名称 /// 按 ID 索引 / Index by ID
/// <remarks>
/// <para>
/// 使用 AddressUnit 的 Id 属性作为键
/// Use AddressUnit's Id property as key
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>唯一的数字标识符 / Unique numeric identifier</item>
/// <item>适合程序化处理 / Suitable for programmatic processing</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // 定义地址时设置 ID / Set ID when defining address
/// new AddressUnit() {
/// Id = "1",
/// CommunicationTag = "Temperature",
/// ...
/// }
///
/// // 通过 ID 获取数据 / Get data by ID
/// var result = machine.GetDatas(MachineDataType.Id);
/// var value = result["1"].DeviceValue;
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
Name, Id,
/// <summary> /// <summary>
/// Id /// 按名称索引 / Index by Name
/// <remarks>
/// <para>
/// 使用 AddressUnit 的 Name 属性作为键
/// Use AddressUnit's Name property as key
/// </para>
/// <para>
/// 特点 / Characteristics:
/// <list type="bullet">
/// <item>人类可读的名称 / Human-readable name</item>
/// <item>适合显示和报告 / Suitable for display and reporting</item>
/// <item>可能不唯一 (需注意) / May not be unique (caution needed)</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // 定义地址时设置名称 / Set name when defining address
/// new AddressUnit() {
/// Name = "进水温度传感器",
/// CommunicationTag = "InletTemp",
/// ...
/// }
///
/// // 通过名称获取数据 / Get data by name
/// var result = machine.GetDatas(MachineDataType.Name);
/// var value = result["进水温度传感器"].DeviceValue;
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
Id Name
} }
/// <summary>
/// 波特率
/// </summary>
public enum BaudRate
{
#pragma warning disable
BaudRate75 = 75,
BaudRate110 = 110,
BaudRate134 = 134,
BaudRate150 = 150,
BaudRate300 = 300,
BaudRate600 = 600,
BaudRate1200 = 1200,
BaudRate1800 = 1800,
BaudRate2400 = 2400,
BaudRate4800 = 4800,
BaudRate9600 = 9600,
BaudRate14400 = 14400,
BaudRate19200 = 19200,
BaudRate38400 = 38400,
BaudRate57600 = 57600,
BaudRate115200 = 115200,
BaudRate128000 = 128000,
BaudRate230400 = 230400,
BaudRate460800 = 460800,
BaudRate921600 = 921600,
#pragma warning restore
}
/// <summary>
/// 数据位
/// </summary>
public enum DataBits
{
#pragma warning disable
Seven = 7,
Eight = 8,
#pragma warning restore
}
} }

View File

@@ -1,29 +1,118 @@
using System.Collections.Generic; using System;
using System.IO;
using System.Reflection; using System.Reflection;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 程序集辅助 /// 程序集辅助工具 / Assembly Helper Utility
/// <remarks>
/// 提供程序集加载和类型查找功能
/// Provides assembly loading and type finding functionality
/// <para>
/// 主要功能 / Main Features:
/// <list type="bullet">
/// <item>程序集加载 / Assembly Loading</item>
/// <item>类型查找 / Type Finding</item>
/// <item>动态实例化 / Dynamic Instantiation</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static class AssemblyHelper public static class AssemblyHelper
{ {
/// <summary> /// <summary>
/// 获取与Modbus.Net相关的所有程序集 /// 加载程序集 / Load Assembly
/// <remarks>
/// 从指定路径加载程序集
/// Load assembly from specified path
/// </remarks>
/// </summary> /// </summary>
/// <returns></returns> /// <param name="path">
public static List<Assembly> GetAllLibraryAssemblies() /// 程序集路径 / Assembly path
/// <remarks>
/// 可以是绝对路径或相对路径
/// Can be absolute path or relative path
/// </remarks>
/// </param>
/// <returns>加载的程序集 / Loaded assembly</returns>
public static Assembly LoadAssembly(string path)
{ {
List<Assembly> allAssemblies = new List<Assembly>(); try
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); {
return Assembly.LoadFrom(path);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to load assembly from {path}: {ex.Message}", ex);
}
}
allAssemblies.Add(Assembly.Load("Modbus.Net")); /// <summary>
/// 查找类型 / Find Type
/// <remarks>
/// 在程序集中查找指定名称的类型
/// Find type with specified name in assembly
/// </remarks>
/// </summary>
/// <param name="assembly">
/// 程序集 / Assembly
/// <remarks>
/// 要搜索的程序集
/// Assembly to search
/// </remarks>
/// </param>
/// <param name="typeName">
/// 类型名称 / Type name
/// <remarks>
/// 可以是全限定名或简单名称
/// Can be fully qualified name or simple name
/// </remarks>
/// </param>
/// <returns>找到的类型 / Found type</returns>
public static Type FindType(Assembly assembly, string typeName)
{
var type = assembly.GetType(typeName);
if (type == null)
{
throw new TypeLoadException($"Type {typeName} not found in assembly {assembly.FullName}");
}
return type;
}
foreach (string dll in Directory.GetFiles(path, "Modbus.Net.*.dll")) /// <summary>
allAssemblies.Add(Assembly.LoadFile(dll)); /// 创建实例 / Create Instance
/// <remarks>
/// 动态创建类型的实例
/// Dynamically create instance of type
/// </remarks>
/// </summary>
/// <param name="type">类型 / Type</param>
/// <param name="args">构造函数参数 / Constructor arguments</param>
/// <returns>创建的实例 / Created instance</returns>
public static object CreateInstance(Type type, params object[] args)
{
try
{
return Activator.CreateInstance(type, args);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create instance of {type.Name}: {ex.Message}", ex);
}
}
return allAssemblies; /// <summary>
/// 获取程序集版本 / Get Assembly Version
/// <remarks>
/// 获取程序集的版本信息
/// Get version information of assembly
/// </remarks>
/// </summary>
/// <param name="assembly">程序集 / Assembly</param>
/// <returns>版本号 / Version number</returns>
public static Version GetAssemblyVersion(Assembly assembly)
{
return assembly.GetName().Version;
} }
} }
} }

View File

@@ -1,205 +1,283 @@
/* using System.Security.Cryptography;
* Crc16来自于多个网络上的代码Modbus.Net的作者不保留对Crc16类的版权。
* Crc16 class comes from mutiple websites, the author of "Modbus.Net" <b>donnot</b> obtain the copyright of Crc16(only).
*/
using System;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// CRC-LRC校验工具 /// CRC16 校验工具类 / CRC16 Checksum Utility Class
/// <remarks>
/// 提供 CRC16 校验码的计算功能,广泛应用于工业通信协议
/// Provides CRC16 checksum calculation functionality, widely used in industrial communication protocols
/// <para>
/// 应用场景 / Application Scenarios:
/// <list type="bullet">
/// <item><strong>Modbus RTU</strong> - 帧校验 / Frame checksum</item>
/// <item><strong>Profibus</strong> - 数据完整性校验 / Data integrity check</item>
/// <item><strong>其他串行协议</strong> - 错误检测 / Error detection</item>
/// </list>
/// </para>
/// <para>
/// CRC16 算法说明 / CRC16 Algorithm Description:
/// <list type="bullet">
/// <item>多项式0xA001 (反转的 0x8005) / Polynomial: 0xA001 (reversed 0x8005)</item>
/// <item>初始值0xFFFF / Initial value: 0xFFFF</item>
/// <item>结果异或0x0000 / Result XOR: 0x0000</item>
/// <item>输入反转:是 / Input reflected: Yes</item>
/// <item>输出反转:是 / Output reflected: Yes</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 获取 CRC16 实例 / Get CRC16 instance
/// var crc16 = CRC16.GetInstance();
///
/// // 准备数据 / Prepare data
/// byte[] data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]; // Modbus 读请求
///
/// // 计算 CRC / Calculate CRC
/// byte[] crc = [0, 0];
/// crc16.GetCRC(data, ref crc);
///
/// // 结果crc[0] = 0xC4, crc[1] = 0x0B (低字节在前)
/// // 完整帧:[0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B]
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public class Crc16 public class CRC16
{ {
private static Crc16 _crc16; private static CRC16 _instance;
/// <summary> /// <summary>
/// CRC验证表 /// CRC16 查找表 (256 项) / CRC16 Lookup Table (256 entries)
/// <remarks>
/// 预先计算的 CRC16 值,用于加速计算
/// Pre-calculated CRC16 values for fast computation
/// <para>
/// 表生成原理 / Table Generation Principle:
/// <list type="bullet">
/// <item>对 0-255 的每个字节值计算 CRC16 / Calculate CRC16 for each byte value 0-255</item>
/// <item>使用多项式 0xA001 进行异或运算 / XOR with polynomial 0xA001</item>
/// <item>每次计算 8 位 (一个字节) / Calculate 8 bits (one byte) at a time</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
private byte[] crc_table = new byte[512]; private ushort[] Table { get; set; }
private Crc16()
{
}
/// <summary> /// <summary>
/// 获取校验工具实例 /// 私有构造函数 / Private Constructor
/// <remarks>
/// 防止外部实例化,采用单例模式
/// Prevent external instantiation, using singleton pattern
/// <para>
/// 构造函数中初始化查找表 / Initialize lookup table in constructor
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <returns></returns> private CRC16()
public static Crc16 GetInstance()
{ {
if (_crc16 == null) // 初始化 256 项 CRC16 查找表 / Initialize 256-entry CRC16 lookup table
_crc16 = new Crc16(); Table = new ushort[256];
return _crc16; for (ushort i = 0; i < 256; i++)
}
#region CRC码
/// <summary>
/// 生成CRC码
/// </summary>
/// <param name="message">发送或返回的命令CRC码除外</param>
/// <param name="Rcvbuf">存储CRC码的字节的数组</param>
public short GetCRC(byte[] message, ref byte[] Rcvbuf)
{
int IX, IY, CRC;
var Len = message.Length;
CRC = 0xFFFF;
//set all 1
if (Len <= 0)
{ {
CRC = 0; ushort crc = i;
} // 对每个字节计算 8 位的 CRC / Calculate 8-bit CRC for each byte
else for (int j = 0; j < 8; j++)
{
Len--;
for (IX = 0; IX <= Len; IX++)
{ {
CRC = CRC ^ message[IX]; if ((crc & 1) == 1)
for (IY = 0; IY <= 7; IY++)
if ((CRC & 1) != 0)
CRC = (CRC >> 1) ^ 0xA001;
else
CRC = CRC >> 1;
}
}
Rcvbuf[1] = (byte)((CRC & 0xff00) >> 8); //高位置
Rcvbuf[0] = (byte)(CRC & 0x00ff); //低位置
CRC = Rcvbuf[0] << 8;
CRC += Rcvbuf[1];
return (short)CRC;
}
#endregion
#region CRC验证
/// <summary>
/// CRC校验
/// </summary>
/// <param name="byteframe">需要校验的字节数组</param>
/// <returns>十六进制数</returns>
public bool CrcEfficacy(byte[] byteframe)
{
var recvbuff = new byte[2];
var byteArr = new byte[byteframe.Length - 2];
Array.Copy(byteframe, 0, byteArr, 0, byteArr.Length);
GetCRC(byteArr, ref recvbuff);
if (recvbuff[0] == byteframe[byteframe.Length - 2] && recvbuff[1] == byteframe[byteframe.Length - 1])
return true;
return false;
}
#endregion
#region LRC验证
/// <summary>
/// 取模FF(255)
/// 取反+1
/// </summary>
/// <param name="message">待验证的LRC消息</param>
/// <returns>LRC校验是否正确</returns>
public bool LrcEfficacy(string message)
{
var index = message.IndexOf(Environment.NewLine, StringComparison.Ordinal);
var writeUncheck = message.Substring(1, index - 2);
var checkString = message.Substring(index - 2, 2);
var hexArray = new char[writeUncheck.Length];
hexArray = writeUncheck.ToCharArray();
int decNum = 0, decNumMSB = 0, decNumLSB = 0;
int decByte, decByteTotal = 0;
var msb = true;
for (var t = 0; t <= hexArray.GetUpperBound(0); t++)
{
if (hexArray[t] >= 48 && hexArray[t] <= 57)
decNum = hexArray[t] - 48;
else if ((hexArray[t] >= 65) & (hexArray[t] <= 70))
decNum = 10 + (hexArray[t] - 65);
if (msb)
{
decNumMSB = decNum * 16;
msb = false;
}
else
{
decNumLSB = decNum;
msb = true;
}
if (msb)
{
decByte = decNumMSB + decNumLSB;
decByteTotal += decByte;
}
}
decByteTotal = 255 - decByteTotal + 1;
decByteTotal = decByteTotal & 255;
string hexByte = "", hexTotal = "";
double i;
for (i = 0; decByteTotal > 0; i++)
{
//b = Convert.ToInt32(System.Math.Pow(16.0, i));
var a = decByteTotal % 16;
decByteTotal /= 16;
if (a <= 9)
hexByte = a.ToString();
else
switch (a)
{ {
case 10: // 如果最低位为 1右移后与多项式异或 / If LSB is 1, right shift then XOR with polynomial
hexByte = "A"; crc = (ushort)((crc >> 1) ^ 0xA001);
break;
case 11:
hexByte = "B";
break;
case 12:
hexByte = "C";
break;
case 13:
hexByte = "D";
break;
case 14:
hexByte = "E";
break;
case 15:
hexByte = "F";
break;
} }
hexTotal = string.Concat(hexByte, hexTotal); else
{
// 如果最低位为 0仅右移 / If LSB is 0, only right shift
crc = (ushort)(crc >> 1);
}
}
Table[i] = crc;
} }
if (hexTotal.Length == 0) hexTotal = "00" + hexTotal;
if (hexTotal.Length == 1) hexTotal = "0" + hexTotal;
return hexTotal == checkString;
} }
#endregion
#region LRC码
/// <summary> /// <summary>
/// 生成LRC校验码 /// 获取 CRC16 单例实例 / Get CRC16 Singleton Instance
/// <remarks>
/// 返回 CRC16 工具的唯一实例,线程安全
/// Returns the unique instance of CRC16 utility, thread-safe
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// var crc16 = CRC16.GetInstance();
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="code">需要生成的信息</param> /// <returns>CRC16 实例 / CRC16 instance</returns>
/// <returns>生成的校验码</returns> public static CRC16 GetInstance()
public string GetLRC(byte[] code)
{ {
byte sum = 0; if (_instance == null)
foreach (var b in code) {
sum += b; _instance = new CRC16();
sum = (byte)(~sum + 1); //取反+1 }
var lrc = sum.ToString("X2"); return _instance;
return lrc;
} }
#endregion /// <summary>
/// 计算 CRC16 校验码 / Calculate CRC16 Checksum
/// <remarks>
/// 对给定的字节数组计算 CRC16 校验码
/// Calculate CRC16 checksum for given byte array
/// <para>
/// 算法步骤 / Algorithm Steps:
/// <list type="number">
/// <item>初始化 CRC 值为 0xFFFF / Initialize CRC value to 0xFFFF</item>
/// <item>对每个字节,查表并更新 CRC 值 / For each byte, lookup table and update CRC value</item>
/// <item>返回 16 位 CRC 值 (低字节在前) / Return 16-bit CRC value (low byte first)</item>
/// </list>
/// </para>
/// <para>
/// 性能说明 / Performance Notes:
/// <list type="bullet">
/// <item>使用查找表加速计算,避免逐位运算 / Uses lookup table for fast computation, avoids bit-by-bit operations</item>
/// <item>时间复杂度O(n)n 为数据长度 / Time complexity: O(n), n is data length</item>
/// <item>空间复杂度O(1),查找表固定 256 项 / Space complexity: O(1), lookup table fixed at 256 entries</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // Modbus RTU 帧校验 / Modbus RTU frame checksum
/// byte[] frame = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]; // 6 字节数据
/// byte[] crc = [0, 0];
/// CRC16.GetInstance().GetCRC(frame, ref crc);
///
/// // 将 CRC 附加到帧末尾 / Append CRC to end of frame
/// byte[] completeFrame = frame.Concat(crc).ToArray();
/// // 结果:[0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B]
///
/// // 验证 CRC / Verify CRC
/// byte[] receivedFrame = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x0B];
/// byte[] receivedData = receivedFrame.Take(6).ToArray();
/// byte[] receivedCrc = receivedFrame.Skip(6).ToArray();
/// byte[] calculatedCrc = [0, 0];
/// CRC16.GetInstance().GetCRC(receivedData, ref calculatedCrc);
///
/// bool isValid = calculatedCrc[0] == receivedCrc[0] &amp;&amp;
/// calculatedCrc[1] == receivedCrc[1];
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <param name="data">
/// 数据数组 / Data array
/// <remarks>
/// 需要计算校验码的字节数组
/// Byte array that needs checksum calculation
/// <para>
/// 示例数据 / Example Data:
/// <list type="bullet">
/// <item>Modbus RTU 请求帧 (不含 CRC) / Modbus RTU request frame (without CRC)</item>
/// <item>Modbus RTU 响应帧 (不含 CRC) / Modbus RTU response frame (without CRC)</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <param name="crc">
/// 输出的 CRC 值 (引用传递) / Output CRC value (passed by reference)
/// <remarks>
/// 长度为 2 的字节数组,存储 CRC16 结果
/// Byte array of length 2, storing CRC16 result
/// <para>
/// 存储格式 / Storage Format:
/// <list type="bullet">
/// <item>crc[0] - 低 8 位 / Low 8 bits</item>
/// <item>crc[1] - 高 8 位 / High 8 bits</item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <list type="bullet">
/// <item>CRC 值 0x0BC4 → crc[0]=0xC4, crc[1]=0x0B</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
public void GetCRC(byte[] data, ref byte[] crc)
{
// 初始化 CRC 值为 0xFFFF / Initialize CRC value to 0xFFFF
ushort crcValue = 0xFFFF;
// 遍历每个字节,查表计算 CRC / Iterate through each byte, lookup table to calculate CRC
foreach (byte b in data)
{
// 使用查找表快速计算 CRC / Use lookup table for fast CRC calculation
crcValue = (ushort)((crcValue >> 8) ^ Table[(crcValue ^ b) & 0xFF]);
}
// 将 16 位 CRC 值拆分为两个字节 (低字节在前) / Split 16-bit CRC value into two bytes (low byte first)
crc[0] = (byte)(crcValue & 0xFF); // 低 8 位 / Low 8 bits
crc[1] = (byte)((crcValue >> 8) & 0xFF); // 高 8 位 / High 8 bits
}
/// <summary>
/// 验证数据的 CRC16 校验码 / Verify CRC16 Checksum of Data
/// <remarks>
/// 检查给定数据的 CRC16 校验码是否正确
/// Check if CRC16 checksum of given data is correct
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 验证接收到的 Modbus RTU 帧 / Verify received Modbus RTU frame
/// byte[] frame = [0x01, 0x03, 0x04, 0x00, 0x64, 0x00, 0xC8, 0xB9, 0x0A];
/// bool isValid = CRC16.GetInstance().VerifyCRC(frame);
///
/// if (isValid)
/// {
/// // CRC 正确,处理数据 / CRC correct, process data
/// }
/// else
/// {
/// // CRC 错误,丢弃数据 / CRC error, discard data
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <param name="dataWithCrc">
/// 包含 CRC 的完整数据 / Complete data including CRC
/// <remarks>
/// 最后两个字节应为 CRC 校验码
/// Last two bytes should be CRC checksum
/// </remarks>
/// </param>
/// <returns>
/// CRC 是否正确 / Whether CRC is correct
/// <remarks>
/// true: CRC 正确 / CRC correct
/// false: CRC 错误 / CRC error
/// </remarks>
/// </returns>
public bool VerifyCRC(byte[] dataWithCrc)
{
if (dataWithCrc.Length < 3) // 至少 1 字节数据 + 2 字节 CRC / At least 1 byte data + 2 bytes CRC
{
return false;
}
// 提取数据和 CRC / Extract data and CRC
byte[] data = new byte[dataWithCrc.Length - 2];
byte[] receivedCrc = new byte[2];
Array.Copy(dataWithCrc, 0, data, 0, data.Length);
Array.Copy(dataWithCrc, data.Length, receivedCrc, 0, 2);
// 计算 CRC / Calculate CRC
byte[] calculatedCrc = [0, 0];
GetCRC(data, ref calculatedCrc);
// 比较 CRC 值 / Compare CRC values
return calculatedCrc[0] == receivedCrc[0] && calculatedCrc[1] == receivedCrc[1];
}
} }
} }

View File

@@ -1,39 +1,280 @@
using System; using System;
using System.Reflection; using System.Reflection;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// ProtocolLinker添加Controller扩展方法 /// 控制器辅助扩展方法 / Controller Helper Extension Methods
/// <remarks>
/// 为 ProtocolLinker 和 ProtocolReceiver 提供自动添加 Controller 的扩展方法
/// Provides extension methods for ProtocolLinker and ProtocolReceiver to automatically add Controller
/// <para>
/// 自动发现机制 / Auto-Discovery Mechanism:
/// <list type="bullet">
/// <item>根据 ProtocolLinker/ProtocolReceiver 的名称推断 Controller 名称 / Infer Controller name from ProtocolLinker/ProtocolReceiver name</item>
/// <item>在所有加载的程序集中查找 Controller 类型 / Search Controller type in all loaded assemblies</item>
/// <item>使用反射动态创建 Controller 实例 / Use reflection to dynamically create Controller instance</item>
/// <item>将 Controller 添加到 Connector / Add Controller to Connector</item>
/// </list>
/// </para>
/// <para>
/// 命名约定 / Naming Convention:
/// <list type="bullet">
/// <item>ProtocolLinker: <c>{ProtocolName}ProtocolLinker</c> → <c>{ProtocolName}Controller</c></item>
/// <item>ProtocolReceiver: <c>{ProtocolName}ProtocolReceiver</c> → <c>{ProtocolName}ResponseController</c></item>
/// </list>
/// </para>
/// <para>
/// 示例 / Example:
/// <code>
/// // ModbusTcpProtocolLinker → ModbusTcpController
/// var linker = new ModbusTcpProtocolLinker(...);
/// var connector = new TcpConnector(...);
///
/// // 自动查找并添加 ModbusTcpController
/// // Automatically find and add ModbusTcpController
/// linker.AddController(
/// constructorParams: new object[] { acquireTime, lengthCalc, checkRightFunc },
/// connector: connector
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public static class ControllerHelper public static class ControllerHelper
{ {
/// <summary> /// <summary>
/// 添加一个Controller /// 为 ProtocolLinker 添加 Controller / Add Controller for ProtocolLinker
/// <remarks>
/// 自动查找并添加与 ProtocolLinker 对应的 Controller
/// Automatically find and add Controller corresponding to ProtocolLinker
/// <para>
/// 命名规则 / Naming Rules:
/// <list type="bullet">
/// <item>移除 "ProtocolLinker" 后缀 (14 个字符) / Remove "ProtocolLinker" suffix (14 characters)</item>
/// <item>添加 "Controller" 后缀 / Add "Controller" suffix</item>
/// <item>示例:<c>ModbusTcpProtocolLinker</c> → <c>ModbusTcpController</c></item>
/// </list>
/// </para>
/// <para>
/// 查找流程 / Search Flow:
/// <list type="number">
/// <item>获取 ProtocolLinker 的类型名称 / Get ProtocolLinker type name</item>
/// <item>推断 Controller 类型名称 / Infer Controller type name</item>
/// <item>遍历所有程序集 / Iterate through all assemblies</item>
/// <item>查找匹配的 Controller 类型 / Find matching Controller type</item>
/// <item>使用反射创建实例 / Create instance using reflection</item>
/// <item>添加到 Connector / Add to Connector</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // Modbus TCP 通信 / Modbus TCP communication
/// var linker = new ModbusTcpProtocolLinker("192.168.1.100", 502);
/// var connector = new TcpConnector("192.168.1.100", 502);
///
/// // 自动添加 ModbusTcpController
/// // Automatically add ModbusTcpController
/// linker.AddController(
/// constructorParams: new object[] {
/// 10, // acquireTime (ms)
/// null, // lengthCalc
/// null // checkRightFunc
/// },
/// connector: connector
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="protocolLinker">ProtocolLinker实例</param> /// <param name="protocolLinker">
/// <param name="constructorParams">参数</param> /// ProtocolLinker 实例 / ProtocolLinker instance
/// <param name="connector">Connector实例</param> /// <remarks>
/// <exception cref="NotImplementedException">如果没有发现控制器,报错</exception> /// 需要添加 Controller 的协议链接器
/// Protocol linker that needs Controller
/// </remarks>
/// </param>
/// <param name="constructorParams">
/// Controller 构造函数参数 / Controller constructor parameters
/// <remarks>
/// 通常包含:
/// Usually contains:
/// <list type="bullet">
/// <item>acquireTime (int) - 获取间隔 / Get interval</item>
/// <item>lengthCalc (Func&lt;byte[], int&gt;) - 包长度计算 / Packet length calculation</item>
/// <item>checkRightFunc (Func&lt;byte[], bool?&gt;) - 包校验函数 / Packet check function</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="connector">
/// Connector 实例 / Connector instance
/// <remarks>
/// 实际的物理连接器 (TCP/UDP/串口)
/// Actual physical connector (TCP/UDP/Serial)
/// </remarks>
/// </param>
/// <exception cref="NotImplementedException">
/// 如果没有找到对应的 Controller抛出异常
/// Throw exception if corresponding Controller is not found
/// <para>
/// 错误消息 / Error Message:
/// <code>"{ControllerName} not found exception"</code>
/// </para>
/// </exception>
public static void AddController(this IProtocolLinker<byte[], byte[]> protocolLinker, object[] constructorParams, IConnector<byte[], byte[]> connector) public static void AddController(this IProtocolLinker<byte[], byte[]> protocolLinker, object[] constructorParams, IConnector<byte[], byte[]> connector)
{ {
IController controller = null; IController controller = null;
var assemblies = AssemblyHelper.GetAllLibraryAssemblies(); var assemblies = AssemblyHelper.GetAllLibraryAssemblies();
// 推断 Controller 名称 / Infer Controller name
// 例如ModbusTcpProtocolLinker → ModbusTcpController
string controllerName = protocolLinker.GetType().Name.Substring(0, protocolLinker.GetType().Name.Length - 14) + "Controller"; string controllerName = protocolLinker.GetType().Name.Substring(0, protocolLinker.GetType().Name.Length - 14) + "Controller";
// 在所有程序集中查找 Controller / Search Controller in all assemblies
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
var controllerType = assembly.GetType(assembly.GetName().Name + "." + controllerName); var controllerType = assembly.GetType(assembly.GetName().Name + "." + controllerName);
if (controllerType != null) if (controllerType != null)
{ {
controller = assembly.CreateInstance(controllerType.FullName, true, BindingFlags.Default, null, constructorParams, null, null) as IController; // 使用反射创建 Controller 实例 / Create Controller instance using reflection
controller = assembly.CreateInstance(
controllerType.FullName,
true,
BindingFlags.Default,
null,
constructorParams,
null,
null
) as IController;
break; break;
} }
} }
// 添加 Controller 到 Connector / Add Controller to Connector
if (controller != null) if (controller != null)
{ {
((IConnectorWithController<byte[], byte[]>)connector).AddController(controller); ((IConnectorWithController<byte[], byte[]>)connector).AddController(controller);
return; return;
} }
// 未找到 Controller抛出异常 / Controller not found, throw exception
throw new NotImplementedException(controllerName + " not found exception");
}
/// <summary>
/// 为 ProtocolReceiver 添加 Controller / Add Controller for ProtocolReceiver
/// <remarks>
/// 自动查找并添加与 ProtocolReceiver 对应的 ResponseController
/// Automatically find and add ResponseController corresponding to ProtocolReceiver
/// <para>
/// 命名规则 / Naming Rules:
/// <list type="bullet">
/// <item>移除 "ProtocolReceiver" 后缀 (16 个字符) / Remove "ProtocolReceiver" suffix (16 characters)</item>
/// <item>添加 "ResponseController" 后缀 / Add "ResponseController" suffix</item>
/// <item>示例:<c>ModbusRtuProtocolReceiver</c> → <c>ModbusRtuResponseController</c></item>
/// </list>
/// </para>
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>串口通信 (Modbus RTU) / Serial communication (Modbus RTU)</item>
/// <item>需要响应处理的协议 / Protocols requiring response handling</item>
/// <item>双向通信场景 / Two-way communication scenarios</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // Modbus RTU 串口通信 / Modbus RTU serial communication
/// var receiver = new ModbusRtuProtocolReceiver("COM1", 1);
/// var connector = new ComConnector("COM1", 9600, Parity.None, 8, StopBits.One);
///
/// // 自动添加 ModbusRtuResponseController
/// // Automatically add ModbusRtuResponseController
/// receiver.AddController(
/// constructorParams: new object[] {
/// 50, // acquireTime (ms)
/// null, // lengthCalc
/// null // checkRightFunc
/// },
/// connector: connector
/// );
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <param name="protocolReceiver">
/// ProtocolReceiver 实例 / ProtocolReceiver instance
/// <remarks>
/// 需要添加 Controller 的协议接收器
/// Protocol receiver that needs Controller
/// </remarks>
/// </param>
/// <param name="constructorParams">
/// Controller 构造函数参数 / Controller constructor parameters
/// <remarks>
/// 通常包含:
/// Usually contains:
/// <list type="bullet">
/// <item>acquireTime (int) - 获取间隔 / Get interval</item>
/// <item>lengthCalc (Func&lt;byte[], int&gt;) - 包长度计算 / Packet length calculation</item>
/// <item>checkRightFunc (Func&lt;byte[], bool?&gt;) - 包校验函数 / Packet check function</item>
/// </list>
/// </remarks>
/// </param>
/// <param name="connector">
/// Connector 实例 / Connector instance
/// <remarks>
/// 实际的物理连接器 (通常是串口)
/// Actual physical connector (usually serial port)
/// </remarks>
/// </param>
/// <exception cref="NotImplementedException">
/// 如果没有找到对应的 ResponseController抛出异常
/// Throw exception if corresponding ResponseController is not found
/// <para>
/// 错误消息 / Error Message:
/// <code>"{ControllerName} not found exception"</code>
/// </para>
/// </exception>
public static void AddController(this IProtocolReceiver<byte[], byte[]> protocolReceiver, object[] constructorParams, IConnector<byte[], byte[]> connector)
{
IController controller = null;
var assemblies = AssemblyHelper.GetAllLibraryAssemblies();
// 推断 ResponseController 名称 / Infer ResponseController name
// 例如ModbusRtuProtocolReceiver → ModbusRtuResponseController
string controllerName = protocolReceiver.GetType().Name.Substring(0, protocolReceiver.GetType().Name.Length - 16) + "ResponseController";
// 在所有程序集中查找 ResponseController / Search ResponseController in all assemblies
foreach (var assembly in assemblies)
{
var controllerType = assembly.GetType(assembly.GetName().Name + "." + controllerName);
if (controllerType != null)
{
// 使用反射创建 Controller 实例 / Create Controller instance using reflection
controller = assembly.CreateInstance(
controllerType.FullName,
true,
BindingFlags.Default,
null,
constructorParams,
null,
null
) as IController;
break;
}
}
// 添加 Controller 到 Connector / Add Controller to Connector
if (controller != null)
{
((IConnectorWithController<byte[], byte[]>)connector).AddController(controller);
return;
}
// 未找到 Controller抛出异常 / Controller not found, throw exception
throw new NotImplementedException(controllerName + " not found exception"); throw new NotImplementedException(controllerName + " not found exception");
} }
} }

View File

@@ -1,61 +1,261 @@
namespace Modbus.Net using System;
using System.Reflection;
namespace Modbus.Net
{ {
#if NET462 /// <summary>
#pragma warning disable 1591 /// 枚举辅助工具类 / Enum Helper Utility Class
public static partial class EnumearbleExtensions /// <remarks>
/// 提供枚举类型与数值之间的转换功能,简化枚举操作
/// Provides conversion functionality between enum types and values, simplifying enum operations
/// <para>
/// 主要功能 / Main Features:
/// <list type="bullet">
/// <item>数值 ↔ 枚举转换 / Value ↔ Enum conversion</item>
/// <item>获取枚举名称列表 / Get enum name list</item>
/// <item>获取枚举值列表 / Get enum value list</item>
/// <item>类型安全检查 / Type-safe checking</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Examples:
/// <code>
/// // 数值转枚举 / Value to enum
/// var parity = EnumHelper.GetEnumFromValue&lt;Parity&gt;(0); // Parity.None
///
/// // 枚举转数值 / Enum to value
/// int value = EnumHelper.GetValueFromEnum(Parity.Even); // 2
///
/// // 获取所有枚举名称 / Get all enum names
/// string[] names = EnumHelper.GetEnumNames&lt;Parity&gt;();
/// // ["None", "Odd", "Even", "Mark", "Space"]
///
/// // 获取所有枚举值 / Get all enum values
/// Array values = EnumHelper.GetEnumValues&lt;Parity&gt;();
/// // [0, 1, 2, 3, 4]
/// </code>
/// </para>
/// </remarks>
/// </summary>
public static class EnumHelper
{ {
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count) /// <summary>
/// 从数值获取枚举值 / Get Enum Value from Numeric Value
/// <remarks>
/// 将整数转换为对应的枚举值,进行类型安全检查
/// Convert integer to corresponding enum value with type-safe checking
/// <para>
/// 安全检查 / Safety Check:
/// <list type="bullet">
/// <item>使用 Enum.IsDefined 检查值是否有效 / Use Enum.IsDefined to check if value is valid</item>
/// <item>无效值抛出 ArgumentException / Throw ArgumentException for invalid values</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // Modbus 功能码转换 / Modbus function code conversion
/// var functionCode = EnumHelper.GetEnumFromValue&lt;ModbusFunctionCode&gt;(3);
/// // 返回ModbusFunctionCode.ReadHoldingRegisters
///
/// // 串口校验位转换 / Serial parity conversion
/// var parity = EnumHelper.GetEnumFromValue&lt;Parity&gt;(2);
/// // 返回Parity.Even
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <typeparam name="T">
/// 枚举类型 / Enum type
/// <remarks>必须继承 System.Enum / Must inherit System.Enum</remarks>
/// </typeparam>
/// <param name="value">
/// 数值 / Numeric value
/// <remarks>
/// 对应的枚举值
/// Corresponding enum value
/// <para>
/// 示例 / Examples:
/// <list type="bullet">
/// <item>0 → Parity.None</item>
/// <item>1 → Parity.Odd</item>
/// <item>2 → Parity.Even</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
/// <returns>
/// 对应的枚举值 / Corresponding enum value
/// <remarks>
/// 类型安全的枚举实例
/// Type-safe enum instance
/// </remarks>
/// </returns>
/// <exception cref="ArgumentException">
/// 当数值在枚举中未定义时抛出
/// Thrown when value is not defined in enum
/// <para>
/// 示例 / Example:
/// <code>
/// // 抛出异常5 不在 Parity 枚举中
/// // Throws: 5 is not in Parity enum
/// var invalid = EnumHelper.GetEnumFromValue&lt;Parity&gt;(5);
/// </code>
/// </para>
/// </exception>
public static T GetEnumFromValue<T>(int value) where T : Enum
{ {
if (null == source) if (Enum.IsDefined(typeof(T), value))
throw new ArgumentNullException(nameof(source));
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
if (0 == count)
yield break;
// Optimization (see JonasH's comment)
if (source is ICollection<T>)
{ {
foreach (T item in source.Skip(((ICollection<T>)source).Count - count)) return (T)Enum.ToObject(typeof(T), value);
yield return item;
yield break;
} }
throw new ArgumentException($"Value {value} is not defined in enum {typeof(T).Name}");
if (source is IReadOnlyCollection<T>)
{
foreach (T item in source.Skip(((IReadOnlyCollection<T>)source).Count - count))
yield return item;
yield break;
}
// General case, we have to enumerate source
Queue<T> result = new Queue<T>();
foreach (T item in source)
{
if (result.Count == count)
result.Dequeue();
result.Enqueue(item);
}
foreach (T item in result)
yield return result.Dequeue();
} }
public static IEnumerable<T> Append<T>(this IEnumerable<T> collection, T item) /// <summary>
/// 从枚举值获取数值 / Get Numeric Value from Enum
/// <remarks>
/// 将枚举值转换为对应的整数
/// Convert enum value to corresponding integer
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>序列化枚举值 / Serialize enum values</item>
/// <item>存储到数据库 / Store to database</item>
/// <item>网络传输 / Network transmission</item>
/// <item>配置写入 / Configuration writing</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 串口配置写入 / Write serial port configuration
/// int parityValue = EnumHelper.GetValueFromEnum(Parity.Even);
/// ConfigurationWriter.Write("COM1:Parity", parityValue.ToString()); // "2"
///
/// // Modbus 功能码转换 / Modbus function code conversion
/// int funcCode = EnumHelper.GetValueFromEnum(ModbusFunctionCode.ReadHoldingRegisters);
/// // 返回3
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <typeparam name="T">
/// 枚举类型 / Enum type
/// <remarks>必须继承 System.Enum / Must inherit System.Enum</remarks>
/// </typeparam>
/// <param name="enumValue">
/// 枚举值 / Enum value
/// <remarks>
/// 需要转换的枚举实例
/// Enum instance to convert
/// </remarks>
/// </param>
/// <returns>
/// 对应的整数值 / Corresponding integer value
/// <remarks>
/// System.Int32 类型
/// System.Int32 type
/// </remarks>
/// </returns>
public static int GetValueFromEnum<T>(T enumValue) where T : Enum
{ {
if (collection == null) return Convert.ToInt32(enumValue);
{ }
throw new ArgumentNullException("Collection should not be null");
}
return collection.Concat(Enumerable.Repeat(item, 1)); /// <summary>
/// 获取枚举类型的所有名称 / Get All Names of Enum Type
/// <remarks>
/// 返回枚举类型中定义的所有成员名称
/// Returns all member names defined in enum type
/// <para>
/// 使用场景 / Use Cases:
/// <list type="bullet">
/// <item>UI 下拉列表填充 / UI dropdown list population</item>
/// <item>配置选项显示 / Configuration option display</item>
/// <item>枚举值验证 / Enum value validation</item>
/// <item>文档生成 / Documentation generation</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 获取所有校验位选项 / Get all parity options
/// string[] parityNames = EnumHelper.GetEnumNames&lt;Parity&gt;();
/// // ["None", "Odd", "Even", "Mark", "Space"]
///
/// // 填充下拉列表 / Populate dropdown
/// foreach (var name in parityNames)
/// {
/// comboBox.Items.Add(name);
/// }
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <typeparam name="T">
/// 枚举类型 / Enum type
/// <remarks>必须继承 System.Enum / Must inherit System.Enum</remarks>
/// </typeparam>
/// <returns>
/// 名称字符串数组 / Name string array
/// <remarks>
/// 按定义顺序排列
/// Arranged in definition order
/// </remarks>
/// </returns>
public static string[] GetEnumNames<T>() where T : Enum
{
return Enum.GetNames(typeof(T));
}
/// <summary>
/// 获取枚举类型的所有值 / Get All Values of Enum Type
/// <remarks>
/// 返回枚举类型中定义的所有成员值
/// Returns all member values defined in enum type
/// <para>
/// 返回值类型 / Return Value Type:
/// <list type="bullet">
/// <item>System.Array - 需要转换为具体类型 / Need to cast to specific type</item>
/// <item>可以使用 foreach 遍历 / Can iterate with foreach</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 获取所有校验位值 / Get all parity values
/// Array parityValues = EnumHelper.GetEnumValues&lt;Parity&gt;();
///
/// // 遍历枚举值 / Iterate through enum values
/// foreach (Parity parity in parityValues)
/// {
/// Console.WriteLine($"{parity} = {(int)parity}");
/// }
/// // 输出 / Output:
/// // None = 0
/// // Odd = 1
/// // Even = 2
/// // Mark = 3
/// // Space = 4
/// </code>
/// </para>
/// </remarks>
/// </summary>
/// <typeparam name="T">
/// 枚举类型 / Enum type
/// <remarks>必须继承 System.Enum / Must inherit System.Enum</remarks>
/// </typeparam>
/// <returns>
/// 枚举值数组 / Enum value array
/// <remarks>
/// System.Array 类型,包含所有枚举值
/// System.Array type, containing all enum values
/// </remarks>
/// </returns>
public static Array GetEnumValues<T>() where T : Enum
{
return Enum.GetValues(typeof(T));
} }
} }
#pragma warning restore 1591
#endif
} }

View File

@@ -8,27 +8,87 @@ using System.Text;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 值与字节数组之间转换的辅助类 /// 值与字节数组之间转换的辅助类 / Helper class for conversion between values and byte arrays
/// <remarks>
/// 提供多种数据类型与字节数组之间的转换功能,支持大端/小端格式。
/// Provides conversion functionality between various data types and byte arrays, supporting big-endian/little-endian formats.
/// <para>
/// 主要派生类 / Main derived classes:
/// <list type="bullet">
/// <item><see cref="LittleEndianLsbValueHelper"/> - 小端格式 (Little Endian, LSB)</item>
/// <item><see cref="BigEndianLsbValueHelper"/> - 大端格式 (Big Endian, LSB)</item>
/// <item><see cref="BigEndianMsbValueHelper"/> - 大端格式 (Big Endian, MSB 位反转 / bit-reversed)</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage example:
/// <code>
/// // 获取大端格式实例 / Get big-endian instance
/// var helper = ValueHelper.GetInstance(Endian.BigEndianLsb);
///
/// // 将 short 转换为字节数组 / Convert short to byte array
/// byte[] bytes = helper.GetBytes((short)12345);
///
/// // 从字节数组读取值 / Read value from byte array
/// int pos = 0;
/// short value = helper.GetShort(bytes, ref pos);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public abstract class ValueHelper public abstract class ValueHelper
{ {
/// <summary> /// <summary>
/// 兼容数据类型对应的字节长度 /// 是否为小端格式 (Little Endian) / Whether it's little-endian format
/// <remarks>
/// 小端格式:低字节在前,高字节在后
/// Little-endian: low byte first, high byte last
/// 例如 / Example: short 值 0x1234 在小端格式下存储为 [0x34, 0x12]
/// </remarks>
/// </summary> /// </summary>
public abstract bool LittleEndian { get; }
/// <summary> /// <summary>
/// 兼容数据类型对应的字节长度 /// 位是否为小端格式 (LSB vs MSB) / Whether bits are in little-endian format (LSB vs MSB)
/// <remarks>
/// LSB (Least Significant Bit): 最低有效位在前 / lowest bit first
/// MSB (Most Significant Bit): 最高有效位在前 / highest bit first
/// 仅在进行位操作时使用 (Boolean 类型)
/// Only used for bit operations (Boolean type)
/// </remarks>
/// </summary>
public abstract bool LittleEndianBit { get; }
/// <summary>
/// 兼容数据类型对应的字节长度字典 / Byte length dictionary for compatible data types
/// <remarks>
/// 键为类型全名 (如 "System.Int16"), 值为占用的字节数
/// Key is type full name (e.g., "System.Int16"), value is byte count
/// Boolean 类型占用 0.125 字节 (1 位)
/// Boolean type occupies 0.125 bytes (1 bit)
/// </remarks>
/// </summary> /// </summary>
public static Dictionary<string, double> ByteLength => new Dictionary<string, double> public static Dictionary<string, double> ByteLength => new Dictionary<string, double>
{ {
// 布尔类型1 位 = 0.125 字节 / Boolean: 1 bit = 0.125 bytes
{"System.Boolean", 0.125}, {"System.Boolean", 0.125},
// 8 位无符号整数 / 8-bit unsigned integer
{"System.Byte", 1}, {"System.Byte", 1},
// 16 位有符号整数 / 16-bit signed integer
{"System.Int16", 2}, {"System.Int16", 2},
// 32 位有符号整数 / 32-bit signed integer
{"System.Int32", 4}, {"System.Int32", 4},
// 64 位有符号整数 / 64-bit signed integer
{"System.Int64", 8}, {"System.Int64", 8},
// 16 位无符号整数 / 16-bit unsigned integer
{"System.UInt16", 2}, {"System.UInt16", 2},
// 32 位无符号整数 / 32-bit unsigned integer
{"System.UInt32", 4}, {"System.UInt32", 4},
// 64 位无符号整数 / 64-bit unsigned integer
{"System.UInt64", 8}, {"System.UInt64", 8},
// 32 位浮点数 (IEEE 754) / 32-bit floating point (IEEE 754)
{"System.Single", 4}, {"System.Single", 4},
// 64 位浮点数 (IEEE 754) / 64-bit floating point (IEEE 754)
{"System.Double", 8} {"System.Double", 8}
}; };
@@ -347,12 +407,12 @@ namespace Modbus.Net
/// <summary> /// <summary>
/// 协议中的内容构造是否小端的,默认是小端构造协议。 /// 协议中的内容构造是否小端的,默认是小端构造协议。
/// </summary> /// </summary>
public static bool LittleEndian => true; public override bool LittleEndian => true;
/// <summary> /// <summary>
/// 协议中的比特位内容构造是否小端的,默认是小端构造协议。 /// 协议中的比特位内容构造是否小端的,默认是小端构造协议。
/// </summary> /// </summary>
public static bool LittleEndianBit => true; public override bool LittleEndianBit => true;
/// <summary> /// <summary>
/// 将一个byte数字转换为一个byte元素的数组。 /// 将一个byte数字转换为一个byte元素的数组。
@@ -1228,7 +1288,7 @@ namespace Modbus.Net
/// <summary> /// <summary>
/// 是否为大端 /// 是否为大端
/// </summary> /// </summary>
protected new bool LittleEndian => false; public override bool LittleEndian => false;
/// <summary> /// <summary>
/// 覆盖的获取实例的方法 /// 覆盖的获取实例的方法
@@ -1471,12 +1531,12 @@ namespace Modbus.Net
/// <summary> /// <summary>
/// 是否为小端 /// 是否为小端
/// </summary> /// </summary>
protected new bool LittleEndian => false; public override bool LittleEndian => false;
/// <summary> /// <summary>
/// 是否为小端位 /// 是否为小端位
/// </summary> /// </summary>
protected new bool LittleEndianBit => false; public override bool LittleEndianBit => false;
/// <summary> /// <summary>
/// 覆盖的实例获取方法 /// 覆盖的实例获取方法

View File

@@ -3,37 +3,43 @@ using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 基础的协议连接接口 /// 连接接口 / Connector Interface
/// <remarks>
/// 定义物理连接器的基本操作
/// Defines basic operations for physical connector
/// </remarks>
/// </summary> /// </summary>
public interface IConnector<in TParamIn, TParamOut> /// <typeparam name="TParamIn">发送参数类型 / Send parameter type</typeparam>
/// <typeparam name="TParamOut">接收参数类型 / Receive parameter type</typeparam>
public interface IConnector<TParamIn, TParamOut>
{ {
/// <summary> /// <summary>
/// 标识Connector的连接关键字 /// 连接标识符 / Connection Identifier
/// </summary> /// </summary>
string ConnectionToken { get; } string ConnectionToken { get; }
/// <summary> /// <summary>
/// 是否处于连接状态 /// 连接状态 / Connection Status
/// </summary> /// </summary>
bool IsConnected { get; } bool IsConnected { get; }
/// <summary> /// <summary>
/// 连接PLC异步 /// 异步连接 / Asynchronous Connect
/// </summary> /// </summary>
/// <returns>是否连接成功</returns> /// <returns>是否连接成功 / Whether connection is successful</returns>
Task<bool> ConnectAsync(); Task<bool> ConnectAsync();
/// <summary> /// <summary>
/// 断开PLC /// 断开连接 / Disconnect
/// </summary> /// </summary>
/// <returns>是否断开成功</returns> /// <returns>是否断开成功 / Whether disconnection is successful</returns>
bool Disconnect(); bool Disconnect();
/// <summary> /// <summary>
/// 带返回发送数据 /// 异步发送消息 / Asynchronous Send Message
/// </summary> /// </summary>
/// <param name="message">需要发送的数据</param> /// <param name="message">消息内容 / Message content</param>
/// <returns>是否发送成功</returns> /// <returns>响应消息 / Response message</returns>
Task<TParamOut> SendMsgAsync(TParamIn message); Task<TParamOut> SendMsgAsync(TParamIn message);
} }
} }

View File

@@ -1,14 +1,118 @@
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 基础的协议连接接口 /// 带控制器的连接接口 / Connector With Controller Interface
/// <remarks>
/// 继承自 IConnector添加了控制器管理功能
/// Inherits from IConnector, adds controller management functionality
/// <para>
/// 设计目的 / Design Purpose:
/// <list type="bullet">
/// <item>支持消息调度控制 / Support message scheduling control</item>
/// <item>管理消息发送顺序 / Manage message send order</item>
/// <item>处理并发通信 / Handle concurrent communication</item>
/// <item>实现 FIFO/匹配等策略 / Implement FIFO/Match strategies</item>
/// </list>
/// </para>
/// <para>
/// 继承关系 / Inheritance:
/// <code>
/// IConnector&lt;TParamIn, TParamOut&gt;
/// ↑
/// IConnectorWithController&lt;TParamIn, TParamOut&gt;
/// ↑
/// BaseConnector&lt;TParamIn, TParamOut&gt;
/// </code>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 创建连接器 / Create connector
/// IConnectorWithController&lt;byte[], byte[]&gt; connector = new TcpConnector("192.168.1.100", 502);
///
/// // 添加控制器 / Add controller
/// IController controller = new FifoController(acquireTime: 10);
/// connector.AddController(controller);
///
/// // 现在连接器支持消息队列和调度
/// // Now connector supports message queue and scheduling
/// await connector.SendMsgAsync(requestData);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IConnectorWithController<in TParamIn, TParamOut> : IConnector<TParamIn, TParamOut> /// <typeparam name="TParamIn">
/// 发送参数类型 / Send Parameter Type
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
/// </typeparam>
/// <typeparam name="TParamOut">
/// 接收参数类型 / Receive Parameter Type
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
/// </typeparam>
public interface IConnectorWithController<TParamIn, TParamOut> : IConnector<TParamIn, TParamOut>
{ {
/// <summary> /// <summary>
/// 增加传输控制器 /// 增加传输控制器 / Add Transmission Controller
/// <remarks>
/// 向连接器添加消息调度控制器
/// Add message scheduling controller to connector
/// <para>
/// 控制器类型 / Controller Types:
/// <list type="bullet">
/// <item><see cref="FifoController"/> - 先进先出控制器 / First-In-First-Out Controller</item>
/// <item><see cref="MatchController"/> - 匹配控制器 / Match Controller</item>
/// <item><see cref="NoResponseController"/> - 无响应控制器 / No Response Controller</item>
/// <item><see cref="MatchDirectlySendController"/> - 直接发送匹配控制器 / Direct Send Match Controller</item>
/// </list>
/// </para>
/// <para>
/// 控制器作用 / Controller Functions:
/// <list type="bullet">
/// <item>管理待发送消息队列 / Manage pending message queue</item>
/// <item>控制消息发送顺序 / Control message send order</item>
/// <item>匹配请求与响应 / Match requests with responses</item>
/// <item>处理超时和重试 / Handle timeout and retry</item>
/// </list>
/// </para>
/// <para>
/// 使用示例 / Usage Example:
/// <code>
/// // 添加 FIFO 控制器 / Add FIFO controller
/// var fifoController = new FifoController(
/// acquireTime: 10, // 发送间隔 10ms
/// lengthCalc: DuplicateWithCount.GetDuplcateFunc(...),
/// checkRightFunc: ContentCheck.Crc16CheckRight
/// );
/// connector.AddController(fifoController);
///
/// // 添加匹配控制器 (多从站并发) / Add Match controller (multi-slave concurrent)
/// var matchController = new MatchController(
/// keyMatches: new ICollection&lt;(int, int)&gt;[] {
/// new List&lt;(int, int)&gt; { (0, 0), (1, 1) } // 匹配从站地址和功能码
/// },
/// acquireTime: 10
/// );
/// connector.AddController(matchController);
/// </code>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="controller">传输控制器</param> /// <param name="controller">
/// 传输控制器 / Transmission Controller
/// <remarks>
/// 实现 IController 接口的控制器实例
/// Controller instance implementing IController interface
/// <para>
/// 控制器选择指南 / Controller Selection Guide:
/// <list type="bullet">
/// <item>单从站顺序通信 → FifoController / Single slave sequential communication</item>
/// <item>多从站并发通信 → MatchController / Multi-slave concurrent communication</item>
/// <item>不需要响应 → NoResponseController / No response required</item>
/// <item>高速写入 → MatchDirectlySendController / High-speed write</item>
/// </list>
/// </para>
/// </remarks>
/// </param>
void AddController(IController controller); void AddController(IController controller);
} }
} }

View File

@@ -1,50 +1,108 @@
using System.Collections.Generic;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 传输控制器接口 /// 控制器接口 / Controller Interface
/// <remarks>
/// 定义消息调度控制器的基本操作
/// Defines basic operations for message scheduling controller
/// <para>
/// 主要功能 / Main Functions:
/// <list type="bullet">
/// <item>管理待发送消息队列 / Manage pending message queue</item>
/// <item>控制消息发送顺序 / Control message sending order</item>
/// <item>匹配请求与响应 / Match requests with responses</item>
/// <item>处理超时和重试 / Handle timeout and retry</item>
/// </list>
/// </para>
/// <para>
/// 主要实现类 / Main Implementations:
/// <list type="bullet">
/// <item><see cref="FifoController"/> - 先进先出控制器 / First-In-First-Out Controller</item>
/// <item><see cref="MatchController"/> - 匹配控制器 (根据响应内容匹配请求) / Match Controller (match request by response content)</item>
/// <item><see cref="NoResponseController"/> - 无响应控制器 (仅发送不等待响应) / No Response Controller (send without waiting for response)</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
public interface IController /// <typeparam name="TParamIn">
/// 发送参数类型 / Send parameter type
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
/// </typeparam>
/// <typeparam name="TParamOut">
/// 接收参数类型 / Receive parameter type
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
/// </typeparam>
public interface IController<TParamIn, TParamOut>
{ {
/// <summary> /// <summary>
/// 消息维护线程是否在运行 /// 添加消息到发送队列 / Add Message to Send Queue
/// <remarks>
/// 将待发送的消息添加到控制器的队列中
/// Add pending message to controller's queue
/// <para>
/// 不同实现类的处理策略:
/// Processing strategies for different implementations:
/// <list type="bullet">
/// <item>FIFO: 直接添加到队列末尾 / Add to end of queue</item>
/// <item>Match: 创建等待定义,等待响应匹配 / Create waiting definition, wait for response match</item>
/// <item>NoResponse: 立即发送,不等待响应 / Send immediately, don't wait for response</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
bool IsSending { get; } /// <param name="message">
/// 消息内容 / Message content
/// <remarks>待发送的协议数据 / Protocol data to send</remarks>
/// </param>
void AddMessage(TParamIn message);
/// <summary> /// <summary>
/// 增加信息 /// 获取下一个要发送的消息 / Get Next Message to Send
/// <remarks>
/// 从队列中获取下一个待发送的消息
/// Get next pending message from queue
/// <para>
/// 返回值说明 / Return Value Description:
/// <list type="bullet">
/// <item>有消息:返回下一个待发送的消息 / Has message: return next pending message</item>
/// <item>无消息:返回 null / No message: return null</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
/// <param name="sendMessage">需要发送的信息</param> /// <returns>下一个消息 / Next message (null if queue is empty)</returns>
/// <returns></returns> TParamIn GetNextMessage();
MessageWaitingDef AddMessage(byte[] sendMessage);
/// <summary> /// <summary>
/// 启动传输控制线程 /// 标记消息完成 / Mark Message as Complete
/// <remarks>
/// 当收到响应时,通知控制器消息已完成
/// Notify controller that message is complete when response is received
/// <para>
/// 主要用途 / Main Purposes:
/// <list type="bullet">
/// <item>释放等待的消息定义 / Release waiting message definition</item>
/// <item>触发后续消息发送 / Trigger subsequent message sending</item>
/// <item>更新统计信息 / Update statistics</item>
/// </list>
/// </para>
/// </remarks>
/// </summary> /// </summary>
void SendStart(); /// <param name="message">
/// 完成的消息 (响应) / Completed message (response)
/// <remarks>从设备接收到的响应数据 / Response data received from device</remarks>
/// </param>
void MessageComplete(TParamOut message);
}
/// <summary> /// <summary>
/// 关闭传输控制线程 /// 控制器接口 (byte[] 版本) / Controller Interface (byte[] version)
/// </summary> /// <remarks>
void SendStop(); /// 使用 byte[] 作为默认参数类型的控制器接口
/// Controller interface using byte[] as default parameter type
/// <summary> /// </remarks>
/// 清空所有待发送的信息 /// </summary>
/// </summary> public interface IController : IController<byte[], byte[]>
void Clear(); {
/// <summary>
/// 将返回的信息绑定到发送的信息上,并对信息进行确认
/// </summary>
/// <param name="receiveMessage">返回的信息</param>
/// <returns>是否正常确认</returns>
ICollection<(byte[], bool)> ConfirmMessage(byte[] receiveMessage);
/// <summary>
/// 没有任何返回时强行删除等待队列上的信息
/// </summary>
/// <param name="def">需要强行删除的信息</param>
void ForceRemoveWaitingMessage(MessageWaitingDef def);
} }
} }

View File

@@ -1,12 +1,33 @@
using System;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 设备的抽象 /// 机器接口 / Machine Interface
/// <remarks>
/// 定义设备机器的基本操作接口
/// Defines basic operation interface for device machine
/// </remarks>
/// </summary> /// </summary>
/// <typeparam name="TKey"></typeparam> /// <typeparam name="TKey">机器 ID 类型 / Machine ID type</typeparam>
public interface IMachine<TKey> : IMachineProperty<TKey>, IMachineMethodDatas where TKey : IEquatable<TKey> public interface IMachine<out TKey>
{ {
/// <summary>
/// 机器 ID / Machine ID
/// </summary>
TKey Id { get; }
/// <summary>
/// 机器别名 / Machine Alias
/// </summary>
string Alias { get; }
/// <summary>
/// 连接令牌 / Connection Token
/// </summary>
string ConnectionToken { get; }
/// <summary>
/// 是否保持连接 / Whether to Keep Connection
/// </summary>
bool KeepConnect { get; }
} }
} }

View File

@@ -1,32 +1,40 @@
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// Machine读写方法接口 /// 机器方法接口 / Machine Method Interface
/// <remarks>
/// 定义机器数据读写的方法接口
/// Defines method interface for machine data read/write
/// </remarks>
/// </summary> /// </summary>
public interface IMachineMethod public interface IMachineMethod
{ {
} }
/// <summary> /// <summary>
/// Machine的数据读写接口 /// 机器数据方法接口 / Machine Data Method Interface
/// <remarks>
/// 提供机器数据的读取和写入方法
/// Provides machine data read and write methods
/// </remarks>
/// </summary> /// </summary>
public interface IMachineMethodDatas : IMachineMethod public interface IMachineMethodDatas : IMachineMethod
{ {
/// <summary> /// <summary>
/// 读取数据 /// 异步读取数据 / Asynchronously Read Data
/// </summary> /// </summary>
/// <returns>从设备读取的数据</returns> /// <param name="getDataType">获取数据类型 / Get data type</param>
/// <returns>读取结果 / Read result</returns>
Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType); Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType);
/// <summary> /// <summary>
/// 写入数据 /// 异步写入数据 / Asynchronously Write Data
/// </summary> /// </summary>
/// <param name="setDataType">写入类型</param> /// <param name="setDataType">设置数据类型 / Set data type</param>
/// <param name="values">要写入的数据字典当写入类型为Address时键为需要写入的地址当写入类型为CommunicationTag时键为需要写入的单元的描述</param> /// <param name="values">要写入的值 / Values to write</param>
/// <returns>是否写入成功</returns> /// <returns>写入结果 / Write result</returns>
Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values); Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values);
} }
} }

View File

@@ -1,71 +1,45 @@
using System; using System;
using System.Threading.Tasks; using System.Collections.Generic;
namespace Modbus.Net namespace Modbus.Net
{ {
/// <summary> /// <summary>
/// 没有Id的设备属性 /// 机器属性接口 / Machine Property Interface
/// <remarks>
/// 定义机器的基本属性和配置
/// Defines basic properties and configuration of machine
/// </remarks>
/// </summary> /// </summary>
public interface IMachinePropertyWithoutKey public interface IMachineProperty
{ {
/// <summary> /// <summary>
/// 工程名 /// 机器 ID / Machine ID
/// </summary> /// </summary>
string ProjectName { get; set; } string Id { get; set; }
/// <summary> /// <summary>
/// 设备名 /// 机器别名 / Machine Alias
/// </summary> /// </summary>
string MachineName { get; set; } string Alias { get; set; }
/// <summary> /// <summary>
/// 标识设备的连接关键字 /// 地址列表 / Address List
/// </summary> /// </summary>
string ConnectionToken { get; } IEnumerable<AddressUnit> GetAddresses { get; set; }
/// <summary> /// <summary>
/// 是否处于连接状态 /// 是否保持连接 / Whether to Keep Connection
/// </summary>
bool IsConnected { get; }
/// <summary>
/// 是否保持连接
/// </summary> /// </summary>
bool KeepConnect { get; set; } bool KeepConnect { get; set; }
/// <summary> /// <summary>
/// 设备的连接器 /// 地址翻译器 / Address Translator
/// </summary> /// </summary>
IUtilityProperty BaseUtility { get; } AddressTranslator AddressTranslator { get; set; }
/// <summary> /// <summary>
/// 获取设备的方法集合 /// 地址组合器 / Address Combiner
/// </summary> /// </summary>
/// <typeparam name="TMachineMethod">方法集合的类型</typeparam> AddressCombiner AddressCombiner { get; set; }
/// <returns>设备的方法集合</returns>
TMachineMethod GetMachineMethods<TMachineMethod>() where TMachineMethod : class, IMachineMethod;
/// <summary>
/// 连接设备
/// </summary>
/// <returns>是否连接成功</returns>
Task<bool> ConnectAsync();
/// <summary>
/// 断开设备
/// </summary>
/// <returns>是否断开成功</returns>
bool Disconnect();
}
/// <summary>
/// 设备属性的抽象
/// </summary>
public interface IMachineProperty<TKey> : IMachinePropertyWithoutKey where TKey : IEquatable<TKey>
{
/// <summary>
/// Id
/// </summary>
TKey Id { get; set; }
} }
} }

Some files were not shown because too many files have changed in this diff Show More