Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b223bc440 | ||
|
|
e7cad88bcf | ||
|
|
7f304e826f | ||
|
|
51ded333d9 | ||
|
|
62e6fcbfb1 | ||
|
|
0f757d6996 | ||
|
|
80d6e843af | ||
|
|
40b2b39d2b | ||
|
|
c3c4125a4b | ||
|
|
b4c57a42d0 | ||
|
|
21c8e34934 | ||
|
|
e38c16e899 | ||
|
|
9cffd005a2 | ||
|
|
a6b467cc96 | ||
|
|
d0707a867a | ||
|
|
cc44c64dc4 | ||
|
|
4ecec7a35e | ||
|
|
34ba703696 | ||
|
|
258da627ac | ||
|
|
c6b5d9b928 | ||
|
|
0d65afd74f | ||
|
|
986bb9b561 | ||
|
|
d2594a3ff9 | ||
|
|
f7507428b8 | ||
|
|
9892eda959 | ||
|
|
e9a8705b03 | ||
|
|
b2bc6c82a9 | ||
|
|
d619bf36a1 | ||
|
|
d23b942464 | ||
|
|
511434d18a | ||
|
|
fac39b0bf1 | ||
|
|
3acd6aa1e0 |
318
.gitignore
vendored
318
.gitignore
vendored
@@ -1,34 +1,84 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
build/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
|
||||
!packages/*/build/
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.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
|
||||
[Tt]est[Rr]esult*/
|
||||
[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
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
@@ -38,26 +88,41 @@ build/
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
@@ -65,6 +130,7 @@ ipch/
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
@@ -72,9 +138,30 @@ _TeamCity*
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.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_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
@@ -93,66 +180,219 @@ DocProject/Help/html
|
||||
publish/
|
||||
|
||||
# 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
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
#packages/
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
# NuGet Packages
|
||||
*.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
|
||||
|
||||
# Windows Store app package directory
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
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
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.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
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
*.mdf
|
||||
*.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
|
||||
GeneratedArtifacts/
|
||||
_Pvt_Extensions/
|
||||
ModelManifest.xml
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# =========================
|
||||
# Windows detritus
|
||||
# =========================
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Mac desktop service store files
|
||||
.DS_Store
|
||||
/Modbus.Net/packages
|
||||
/Modbus.Net/.vs
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# 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
|
||||
@@ -2,40 +2,259 @@ using System;
|
||||
|
||||
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
|
||||
{
|
||||
/// <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;
|
||||
|
||||
/// <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
|
||||
{
|
||||
private static BigEndian3412ValueHelper _bigEndian3412Instance;
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 保护的构造函数,防止外部实例化
|
||||
/// Protected constructor to prevent external instantiation
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected BigEndian3412ValueHelper()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 覆写的实例获取
|
||||
/// 实例获取 / Instance Get
|
||||
/// <remarks>
|
||||
/// 重写基类的实例获取方法
|
||||
/// Overrides base class instance get method
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected override ValueHelper _Instance => _bigEndian3412Instance;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为大端
|
||||
/// 是否为大端 / Whether Big Endian
|
||||
/// <remarks>
|
||||
/// 返回 true (实际上是基于大端的 3412 变体)
|
||||
/// Returns true (actually a 3412 variant based on big-endian)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected new bool LittleEndian => false;
|
||||
|
||||
protected new bool LittleEndianBit => false;
|
||||
public override bool LittleEndian => true;
|
||||
|
||||
/// <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>
|
||||
public new static BigEndian3412ValueHelper Instance
|
||||
=> _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)
|
||||
{
|
||||
Array.Reverse(data, pos, 4);
|
||||
@@ -49,5 +268,250 @@ namespace Modbus.Net
|
||||
pos += 4;
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
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]
|
||||
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)
|
||||
{
|
||||
var source = $@"
|
||||
@@ -26,15 +58,37 @@ namespace Modbus.Net
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventHandlerConnector 代码生成器 / EventHandlerConnector Code Generator
|
||||
/// <remarks>
|
||||
/// 使用 Roslyn 源生成器自动生成 EventHandlerConnector 的部分代码
|
||||
/// Automatically generates partial code for EventHandlerConnector using Roslyn source generator
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[Generator]
|
||||
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)
|
||||
{
|
||||
var source = $@"
|
||||
@@ -55,37 +109,100 @@ namespace Modbus.Net
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ConnectorWithControllerByteArray 代码内容生成类 / ConnectorWithControllerByteArray Code Content Generation Class
|
||||
/// <remarks>
|
||||
/// 提供 BaseConnector 和 EventHandlerConnector 的通用代码内容
|
||||
/// Provides common code content for BaseConnector and EventHandlerConnector
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
return new StringBuilder(@"
|
||||
/// <summary>
|
||||
/// 发送锁
|
||||
/// 发送锁 / Send Lock
|
||||
/// <remarks>
|
||||
/// 用于保护并发发送操作
|
||||
/// Used to protect concurrent send operations
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected abstract AsyncLock Lock { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为全双工
|
||||
/// 是否为全双工 / Whether Full-Duplex
|
||||
/// <remarks>
|
||||
/// true: 全双工 (可同时收发)
|
||||
/// false: 半双工 (交替收发)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public bool IsFullDuplex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送超时时间
|
||||
/// 发送超时时间 / Send Timeout Time
|
||||
/// <remarks>
|
||||
/// 发送操作的超时时间 (毫秒)
|
||||
/// Timeout time for send operations (milliseconds)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected abstract int TimeoutTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造器 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化连接器,设置超时和双工模式
|
||||
/// Initialize connector, set timeout and duplex mode
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name=""timeoutTime"">发送超时时间</param>
|
||||
/// <param name=""isFullDuplex"">是否为全双工</param>
|
||||
/// <param name=""timeoutTime"">
|
||||
/// 发送超时时间 / 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)
|
||||
{
|
||||
IsFullDuplex = isFullDuplex;
|
||||
@@ -98,61 +215,13 @@ namespace Modbus.Net
|
||||
{
|
||||
var ans = await SendMsgInner(message);
|
||||
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();
|
||||
|
||||
// ... 更多代码 ...
|
||||
");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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"))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
563
Modbus.Net/Modbus.Net.HJ212/HJ212Enums.cs
Normal file
563
Modbus.Net/Modbus.Net.HJ212/HJ212Enums.cs
Normal 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
|
||||
}
|
||||
@@ -1,101 +1,307 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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<string, string>(
|
||||
/// id: "Station001",
|
||||
/// alias: "1#监测站",
|
||||
/// connectionString: "192.168.1.100:9002",
|
||||
/// st: "32", // 水污染源
|
||||
/// pw: "123456", // 密码
|
||||
/// mn: "888888888888888001" // 设备标识
|
||||
/// );
|
||||
///
|
||||
/// // 上报实时数据 / Report real-time data
|
||||
/// var data = new Dictionary<string, double>
|
||||
/// {
|
||||
/// { "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>
|
||||
where TUnitKey : IEquatable<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;
|
||||
|
||||
protected string ST { get; }
|
||||
|
||||
protected string CN { get; }
|
||||
|
||||
protected string PW { get; }
|
||||
|
||||
protected string MN { get; }
|
||||
|
||||
private int ErrorCount { get; set; }
|
||||
/// <summary>
|
||||
/// 错误计数 / Error Count
|
||||
/// <remarks>
|
||||
/// 当前连续错误次数
|
||||
/// Current consecutive error count
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private int _errorCount = 0;
|
||||
|
||||
/// <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>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
public HJ212Machine(TKey id, string connectionString, string st, string cn, string pw, string mn)
|
||||
: base(id, null, true)
|
||||
protected string ST { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码 (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);
|
||||
ST = st;
|
||||
CN = cn;
|
||||
PW = pw;
|
||||
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<bool>:
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
{
|
||||
//检测并连接设备
|
||||
// 检查连接 / Check connection
|
||||
if (!BaseUtility.IsConnected)
|
||||
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>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
//根据设置类型找到对应的地址描述
|
||||
formatValues.Add(value.Key, value.Value.ToString());
|
||||
formatValues.Add(value.Key, value.Value.ToString("F2"));
|
||||
}
|
||||
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)
|
||||
BaseUtility.Disconnect();
|
||||
|
||||
return new ReturnStruct<bool>
|
||||
{
|
||||
Datas = true,
|
||||
IsSuccess = true,
|
||||
ErrorCode = 0,
|
||||
ErrorMsg = ""
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorCount++;
|
||||
logger.LogError(e, $"BaseMachine -> SetDatas, Id:{Id} Connection:{ConnectionToken} error. ErrorCount {ErrorCount}.");
|
||||
_errorCount++;
|
||||
logger.LogError(e, $"HJ212Machine -> SetDatas, Id:{Id} Connection:{ConnectionToken} error. ErrorCount {_errorCount}.");
|
||||
|
||||
if (ErrorCount >= _maxErrorCount)
|
||||
if (_errorCount >= _maxErrorCount)
|
||||
Disconnect();
|
||||
return new ReturnStruct<bool>()
|
||||
|
||||
return new ReturnStruct<bool>
|
||||
{
|
||||
Datas = false,
|
||||
IsSuccess = false,
|
||||
ErrorCode = -100,
|
||||
ErrorMsg = "Unknown Exception"
|
||||
ErrorMsg = e.Message
|
||||
};
|
||||
}
|
||||
return new ReturnStruct<bool>()
|
||||
{
|
||||
Datas = true,
|
||||
IsSuccess = true,
|
||||
ErrorCode = 0,
|
||||
ErrorMsg = ""
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -6,71 +6,488 @@ using System.Threading.Tasks;
|
||||
namespace Modbus.Net.HJ212
|
||||
{
|
||||
/// <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<SystemLoginHJ212OutputStruct>(
|
||||
/// protocol[typeof(SystemLoginHJ212Protocol)],
|
||||
/// loginInput
|
||||
/// );
|
||||
///
|
||||
/// // 心跳 / Heartbeat
|
||||
/// var heartbeatInput = new HeartbeatHJ212InputStruct("32", "123456", "888888888888888001");
|
||||
/// var heartbeatOutput = await protocol.SendReceiveAsync<HeartbeatHJ212OutputStruct>(
|
||||
/// protocol[typeof(HeartbeatHJ212Protocol)],
|
||||
/// heartbeatInput
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class HJ212Protocol : BaseProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <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)
|
||||
: 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>
|
||||
/// 连接
|
||||
/// 连接设备 / Connect Device
|
||||
/// <remarks>
|
||||
/// 建立与 HJ212 设备的 TCP 连接
|
||||
/// Establish TCP connection with HJ212 device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns>是否连接成功</returns>
|
||||
/// <returns>是否连接成功 / Whether Connection is Successful</returns>
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
return await ProtocolLinker.ConnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
#region 写数据
|
||||
#region 系统登录 / System Login
|
||||
|
||||
/// <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>
|
||||
public class WriteRequestHJ212Protocol : ProtocolUnit<byte[], byte[]>
|
||||
{
|
||||
/// <summary>
|
||||
/// 从对象的参数数组格式化
|
||||
/// 格式化写请求 / Format Write Request
|
||||
/// <remarks>
|
||||
/// 将写输入结构转换为 ASCII 字符串
|
||||
/// Convert write input structure to ASCII string
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="message">非结构化的输入数据</param>
|
||||
/// <returns>格式化后的字节流</returns>
|
||||
/// <param name="message">写输入结构 / Write Input Structure</param>
|
||||
/// <returns>ASCII 编码的写请求 / ASCII-encoded Write Request</returns>
|
||||
public override byte[] Format(IInputStruct message)
|
||||
{
|
||||
var r_message = (WriteRequestHJ212InputStruct)message;
|
||||
string formatMessage = "##0633";
|
||||
string formatMessage = "";
|
||||
formatMessage += "QN=" + r_message.QN + ";";
|
||||
formatMessage += "ST=" + r_message.ST + ";";
|
||||
formatMessage += "CN=" + r_message.CN + ";";
|
||||
formatMessage += "PW=" + r_message.PW + ";";
|
||||
formatMessage += "MN=" + r_message.MN + ";";
|
||||
formatMessage += "CP=&&";
|
||||
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 += "&&";
|
||||
// TODO: 添加数据内容 / Add data content
|
||||
return Encoding.ASCII.GetBytes(formatMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把仪器返回的内容填充到输出结构中
|
||||
/// 解析写响应 / Parse Write Response
|
||||
/// <remarks>
|
||||
/// 将 ASCII 响应字符串转换为输出结构
|
||||
/// Convert ASCII response string to output structure
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="messageBytes">返回数据的字节流</param>
|
||||
/// <param name="pos">转换标记位</param>
|
||||
/// <returns>结构化的输出数据</returns>
|
||||
/// <param name="messageBytes">响应字节数组 / Response Byte Array</param>
|
||||
/// <param name="pos">解析位置 / Parse Position</param>
|
||||
/// <returns>写输出结构 / Write Output Structure</returns>
|
||||
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
|
||||
{
|
||||
return new WriteRequestHJ212OutputStruct(Encoding.ASCII.GetString(messageBytes));
|
||||
@@ -78,53 +495,103 @@ namespace Modbus.Net.HJ212
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写数据输入
|
||||
/// 写请求输入结构 / Write Request Input Structure
|
||||
/// <remarks>
|
||||
/// 包含写操作所需的参数
|
||||
/// Contains parameters required for write operation
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
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;
|
||||
CN = cn;
|
||||
PW = pw;
|
||||
MN = mn;
|
||||
CP = cp;
|
||||
Datetime = datetime;
|
||||
DataList = dataList;
|
||||
}
|
||||
|
||||
public string QN => "20170101000926706";
|
||||
/// <summary>
|
||||
/// 请求编号 (时间戳) / Request Number (Timestamp)
|
||||
/// </summary>
|
||||
public string QN { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 污染物类型 / Pollutant Type
|
||||
/// </summary>
|
||||
public string ST { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 命令编号 / Command Number
|
||||
/// <remarks>
|
||||
/// 如 "2061" - 设备时间设置
|
||||
/// e.g., "2061" - Device time set
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public string CN { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码 / Password
|
||||
/// </summary>
|
||||
public string PW { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备标识 / Device ID
|
||||
/// </summary>
|
||||
public string MN { get; }
|
||||
|
||||
public List<Dictionary<string, string>> CP { get; }
|
||||
|
||||
public DateTime Datetime { get; }
|
||||
/// <summary>
|
||||
/// 数据列表 / Data List
|
||||
/// <remarks>
|
||||
/// 键值对列表,包含要写入的数据
|
||||
/// Key-value pair list containing data to write
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public List<Dictionary<string, string>> DataList { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写数据输出
|
||||
/// 写请求输出结构 / Write Request Output Structure
|
||||
/// <remarks>
|
||||
/// 包含写操作响应的数据
|
||||
/// Contains write operation response data
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class WriteRequestHJ212OutputStruct : IOutputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化写响应
|
||||
/// Initialize write response
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="value">读取的数据</param>
|
||||
/// <param name="value">响应字符串 / Response String</param>
|
||||
public WriteRequestHJ212OutputStruct(string value)
|
||||
{
|
||||
GetValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取的地址
|
||||
/// 响应值 / Response Value
|
||||
/// </summary>
|
||||
public string GetValue { get; private set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,207 @@
|
||||
namespace Modbus.Net.HJ212
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.HJ212
|
||||
{
|
||||
/// <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>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">接收协议的内容</param>
|
||||
/// <returns>协议是否是正确的</returns>
|
||||
/// <param name="content">
|
||||
/// 发送的数据 / 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)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,308 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Modbus.Net.HJ212
|
||||
{
|
||||
/// <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>
|
||||
public class HJ212ProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="content">扩展前的原始协议内容</param>
|
||||
/// <returns>扩展后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 扩展前的原始协议内容 / 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)
|
||||
{
|
||||
var crc = new byte[2];
|
||||
//Modbus/Rtu协议扩张,增加CRC校验
|
||||
var newFormat = new byte[content.Length + 4];
|
||||
Crc16.GetInstance().GetCRC(content, ref crc);
|
||||
Array.Copy(content, 0, newFormat, 0, content.Length);
|
||||
string crcString = BitConverter.ToString(crc).Replace("-", string.Empty);
|
||||
var crcCalc = Encoding.ASCII.GetBytes(crcString);
|
||||
Array.Copy(crcCalc, 0, newFormat, newFormat.Length - 4, 4);
|
||||
return newFormat;
|
||||
// 计算数据段长度 (包含##和&&CRC)
|
||||
// Calculate data segment length (includes ## and &&CRC)
|
||||
int totalLength = content.Length + 10; // 6(##FL) + 4(CRC)
|
||||
string lengthString = totalLength.ToString("0000");
|
||||
|
||||
// 构建头部:## + 长度 / Build header: ## + length
|
||||
byte[] header = Encoding.ASCII.GetBytes("##" + lengthString);
|
||||
|
||||
// 计算 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>
|
||||
/// 协议收缩,协议内容接收后调用
|
||||
/// 协议收缩 (接收后调用) / 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>
|
||||
/// <param name="content">收缩前的完整协议内容</param>
|
||||
/// <returns>收缩后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 收缩前的完整协议内容 / 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)
|
||||
{
|
||||
//Modbus/Rtu协议收缩,抛弃后面2个字节的内容
|
||||
var newContent = new byte[content.Length - 2];
|
||||
Array.Copy(content, 0, newContent, 0, newContent.Length);
|
||||
// 移除头部 (##FL) 和尾部 (&&CRC##)
|
||||
// Remove header (##FL) and tail (&&CRC##)
|
||||
|
||||
// 最小包长度检查 / 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;
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
@@ -1,53 +1,301 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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>
|
||||
{
|
||||
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)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
// 创建 HJ212 协议实例 / Create HJ212 protocol instance
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetConnectionType(int connectionType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents)
|
||||
/// <summary>
|
||||
/// 读取数据 / Read Data
|
||||
/// <remarks>
|
||||
/// 实现 BaseUtility 的抽象方法,读取 HJ212 设备数据
|
||||
/// Implements abstract method from BaseUtility, reads HJ212 device data
|
||||
/// <para>
|
||||
/// 地址格式 / Address Format:
|
||||
/// <code>ST=32;CN=2011;StartTime=20250327100000;EndTime=20250327110000</code>
|
||||
/// <list type="bullet">
|
||||
/// <item><strong>ST</strong> - 污染物类型 / Pollutant type</item>
|
||||
/// <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<byte[]> 包含:
|
||||
/// ReturnStruct<byte[]> 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
|
||||
{
|
||||
var writeRequestHJ212InputStruct =
|
||||
new WriteRequestHJ212InputStruct((string)setContents[0], (string)setContents[1], (string)setContents[2], (string)setContents[3], (List<Dictionary<string, string>>)setContents[4], (DateTime)setContents[5]);
|
||||
var writeRequestOpcOutputStruct =
|
||||
await
|
||||
Wrapper.SendReceiveAsync<WriteRequestHJ212OutputStruct>(Wrapper[typeof(WriteRequestHJ212Protocol)],
|
||||
writeRequestHJ212InputStruct);
|
||||
return new ReturnStruct<bool>
|
||||
// 解析地址格式 / Parse address format
|
||||
// "ST=32;CN=2011;StartTime=20250327100000;EndTime=20250327110000"
|
||||
var parts = startAddress.Split(';');
|
||||
string st = "32", cn = "2011", startTime = "", endTime = "";
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
Datas = writeRequestOpcOutputStruct?.GetValue != null,
|
||||
IsSuccess = writeRequestOpcOutputStruct?.GetValue != null,
|
||||
if (part.StartsWith("ST=")) st = part.Substring(3);
|
||||
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,
|
||||
ErrorMsg = null,
|
||||
ErrorMsg = null
|
||||
};
|
||||
}
|
||||
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<Dictionary>) - 数据列表</item>
|
||||
/// <item>DateTime - 时间戳</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="setOriginalCount">设置原始长度 / Set Original Length</param>
|
||||
/// <returns>
|
||||
/// 是否设置成功 / Whether Set is Successful
|
||||
/// <remarks>
|
||||
/// ReturnStruct<bool>:
|
||||
/// <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>
|
||||
{
|
||||
Datas = false,
|
||||
|
||||
@@ -1,7 +1,233 @@
|
||||
Modbus.Net.HJ212
|
||||
===================
|
||||
# Modbus.Net.HJ212
|
||||
|
||||
[](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!
|
||||
|
||||
@@ -1,31 +1,162 @@
|
||||
namespace Modbus.Net.Modbus.NA200H
|
||||
namespace Modbus.Net.Modbus.NA200H
|
||||
{
|
||||
/// <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>
|
||||
public class AddressFormaterNA200H : AddressFormater<int, int>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="area">地址区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <returns>格式化的地址字符串</returns>
|
||||
/// <param name="area">
|
||||
/// 地址区域 / Address Area
|
||||
/// <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)
|
||||
{
|
||||
return area + " " + address;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="area">地址区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <param name="subAddress">比特位地址</param>
|
||||
/// <returns>格式化的地址字符串</returns>
|
||||
/// <param name="area">
|
||||
/// 地址区域 / Address Area
|
||||
/// <remarks>NA200H 区域标识 / NA200H 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)
|
||||
{
|
||||
return area + " " + address + "." + subAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.Modbus.NA200H
|
||||
{
|
||||
/// <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>
|
||||
public class AddressTranslatorNA200H : ModbusTranslatorBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 读功能码
|
||||
/// 读功能码字典 / Read Function Code Dictionary
|
||||
/// <remarks>
|
||||
/// 存储各区域的读功能码和字节宽度
|
||||
/// Stores read function codes and byte widths for each area
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary;
|
||||
|
||||
/// <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>
|
||||
protected Dictionary<string, int> TransDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// 写功能码
|
||||
/// 写功能码字典 / Write Function Code Dictionary
|
||||
/// <remarks>
|
||||
/// 存储各区域的写功能码和字节宽度,区分单写和多写
|
||||
/// Stores write function codes and byte widths for each area, distinguishing single and multi-write
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化 NA200H 地址翻译器,配置所有映射关系
|
||||
/// Initialize NA200H address translator, configure all mapping relationships
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public AddressTranslatorNA200H()
|
||||
{
|
||||
// 初始化地址转换字典 / Initialize address translation dictionary
|
||||
TransDictionary = new Dictionary<string, int>
|
||||
{
|
||||
{"Q", 0},
|
||||
{"M", 10000},
|
||||
{"N", 30000},
|
||||
{"I", 0},
|
||||
{"S", 10000},
|
||||
{"IW", 0},
|
||||
{"SW", 5000},
|
||||
{"MW", 0},
|
||||
{"QW", 20000},
|
||||
{"NW", 21000}
|
||||
{"Q", 0}, // 输出继电器 0-9999 / Output relay 0-9999
|
||||
{"M", 10000}, // 辅助继电器 10000-19999 / Auxiliary relay 10000-19999
|
||||
{"N", 30000}, // 特殊继电器 30000-39999 / Special relay 30000-39999
|
||||
{"I", 0}, // 输入继电器 0-9999 / Input relay 0-9999
|
||||
{"S", 10000}, // 状态继电器 10000-19999 / Status relay 10000-19999
|
||||
{"IW", 0}, // 输入寄存器 0-4999 / Input register 0-4999
|
||||
{"SW", 5000}, // 特殊寄存器 5000-9999 / Special register 5000-9999
|
||||
{"MW", 0}, // 辅助寄存器 0-9999 / Auxiliary register 0-9999
|
||||
{"QW", 20000}, // 输出寄存器 20000-29999 / Output register 20000-29999
|
||||
{"NW", 21000} // 特殊寄存器 21000-21999 / Special register 21000-21999
|
||||
};
|
||||
|
||||
// 初始化读功能码字典 / Initialize read function code dictionary
|
||||
ReadFunctionCodeDictionary = new Dictionary<string, AreaOutputDef>
|
||||
{
|
||||
{
|
||||
@@ -103,6 +180,8 @@ namespace Modbus.Net.Modbus.NA200H
|
||||
new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadHoldRegister, AreaWidth = 2}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化写功能码字典 / Initialize write function code dictionary
|
||||
WriteFunctionCodeDictionary = new Dictionary<(string, bool), AreaOutputDef>
|
||||
{
|
||||
{
|
||||
@@ -205,12 +284,58 @@ namespace Modbus.Net.Modbus.NA200H
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <param name="isSingle">是否只写入一个数据</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
/// <param name="address">
|
||||
/// 格式化的地址 / Formatted Address
|
||||
/// <remarks>
|
||||
/// 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)
|
||||
{
|
||||
address = address.ToUpper();
|
||||
@@ -246,10 +371,35 @@ namespace Modbus.Net.Modbus.NA200H
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="area">区域名称</param>
|
||||
/// <returns>字节长度</returns>
|
||||
/// <param name="area">
|
||||
/// 区域名称 / 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)
|
||||
{
|
||||
return ReadFunctionCodeDictionary[area].AreaWidth;
|
||||
|
||||
@@ -1,24 +1,155 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
{
|
||||
/// <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>
|
||||
public interface IUtilityMethodTime : IUtilityMethod
|
||||
{
|
||||
/// <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>
|
||||
/// <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<DateTime> 包含:
|
||||
/// ReturnStruct<DateTime> 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);
|
||||
|
||||
/// <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>
|
||||
/// <param name="setTime">设置PLC时间</param>
|
||||
/// <returns>设置是否成功</returns>
|
||||
/// <param name="startAddress">
|
||||
/// 起始地址 / 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<bool>:
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,202 @@
|
||||
using System;
|
||||
using System;
|
||||
using ProtocolUnit = Modbus.Net.ProtocolUnit<byte[], byte[]>;
|
||||
|
||||
namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
{
|
||||
/// <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>
|
||||
public enum ModbusProtocolTimeFunctionCode : byte
|
||||
{
|
||||
/// <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>
|
||||
GetSystemTime = 3,
|
||||
|
||||
/// <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>
|
||||
SetSystemTime = 16
|
||||
}
|
||||
|
||||
#region 读PLC时间
|
||||
#region 读 PLC 时间 / Read PLC Time
|
||||
|
||||
/// <summary>
|
||||
/// 读时间输入
|
||||
/// 读时间输入结构 / Read Time Input Structure
|
||||
/// <remarks>
|
||||
/// 封装读取设备时间的请求参数
|
||||
/// Encapsulates request parameters for reading device time
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class GetSystemTimeModbusInputStruct : IInputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化读时间请求参数
|
||||
/// Initialize read time request parameters
|
||||
/// </remarks>
|
||||
/// </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)
|
||||
{
|
||||
SlaveAddress = slaveAddress;
|
||||
FunctionCode = (byte)ModbusProtocolTimeFunctionCode.GetSystemTime;
|
||||
StartAddress = startAddress;
|
||||
GetCount = 5;
|
||||
GetCount = 5; // 时间占用 5 个寄存器 / Time occupies 5 registers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从站号
|
||||
/// 从站号 / Slave Address
|
||||
/// <remarks>
|
||||
/// Modbus 从站地址
|
||||
/// Modbus slave address
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public byte SlaveAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能码
|
||||
/// 功能码 / Function Code
|
||||
/// <remarks>
|
||||
/// 读时间功能码 (3)
|
||||
/// Read time function code (3)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public byte FunctionCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始地址
|
||||
/// 开始地址 / Start Address
|
||||
/// <remarks>
|
||||
/// 时间寄存器起始地址
|
||||
/// Starting address of time registers
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort StartAddress { get; }
|
||||
|
||||
/// <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>
|
||||
public ushort GetCount { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读时间输出
|
||||
/// 读时间输出结构 / Read Time Output Structure
|
||||
/// <remarks>
|
||||
/// 封装读取设备时间的响应数据
|
||||
/// Encapsulates response data for reading device time
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class GetSystemTimeModbusOutputStruct : IOutputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化读时间响应数据
|
||||
/// Initialize read time response data
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="functionCode">功能码</param>
|
||||
/// <param name="writeByteCount">写入个数</param>
|
||||
/// <param name="year">年</param>
|
||||
/// <param name="day">日</param>
|
||||
/// <param name="month">月</param>
|
||||
/// <param name="hour">时</param>
|
||||
/// <param name="second">秒</param>
|
||||
/// <param name="minute">分</param>
|
||||
/// <param name="millisecond">毫秒</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="functionCode">功能码 / Function Code</param>
|
||||
/// <param name="writeByteCount">写入个数 / Write Byte Count</param>
|
||||
/// <param name="year">年 / Year</param>
|
||||
/// <param name="day">日 / Day</param>
|
||||
/// <param name="month">月 / Month</param>
|
||||
/// <param name="hour">时 / Hour</param>
|
||||
/// <param name="second">秒 / Second</param>
|
||||
/// <param name="minute">分 / Minute</param>
|
||||
/// <param name="millisecond">毫秒 / Millisecond</param>
|
||||
public GetSystemTimeModbusOutputStruct(byte slaveAddress, byte functionCode,
|
||||
byte writeByteCount, ushort year, byte day, byte month, ushort hour, byte second, byte minute,
|
||||
ushort millisecond)
|
||||
@@ -88,36 +208,64 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从站号
|
||||
/// 从站号 / Slave Address
|
||||
/// </summary>
|
||||
public byte SlaveAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能码
|
||||
/// 功能码 / Function Code
|
||||
/// </summary>
|
||||
public byte FunctionCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入个数
|
||||
/// 写入个数 / Write Byte Count
|
||||
/// </summary>
|
||||
public byte WriteByteCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// 时间 / Time
|
||||
/// <remarks>
|
||||
/// 解析后的 DateTime 对象
|
||||
/// Parsed DateTime object
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public DateTime Time { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读系统时间协议
|
||||
/// 读系统时间协议类 / Read System Time Protocol Class
|
||||
/// <remarks>
|
||||
/// 实现读系统时间的协议格式化和反格式化
|
||||
/// Implements protocol formatting and unformatting for reading system time
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class GetSystemTimeModbusProtocol : ProtocolUnit
|
||||
{
|
||||
/// <summary>
|
||||
/// 格式化
|
||||
/// 格式化 (发送前) / Format (Before Sending)
|
||||
/// <remarks>
|
||||
/// 将读时间请求参数转换为字节数组
|
||||
/// Convert read time request parameters to byte array
|
||||
/// <para>
|
||||
/// 格式 / Format:
|
||||
/// <code>[从站地址][功能码][起始地址 (2)][读取数量 (2)]</code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="message">写系统时间参数</param>
|
||||
/// <returns>写系统时间的核心</returns>
|
||||
/// <param name="message">
|
||||
/// 读时间输入结构 / 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)
|
||||
{
|
||||
var r_message = (GetSystemTimeModbusInputStruct)message;
|
||||
@@ -126,11 +274,48 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="messageBytes">获取的信息</param>
|
||||
/// <param name="flag">当前反格式化的位置</param>
|
||||
/// <returns>反格式化的信息</returns>
|
||||
/// <param name="messageBytes">
|
||||
/// 获取的信息 / Received Information
|
||||
/// <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)
|
||||
{
|
||||
var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
||||
@@ -150,25 +335,34 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
|
||||
#endregion
|
||||
|
||||
#region 写PLC时间
|
||||
#region 写 PLC 时间 / Write PLC Time
|
||||
|
||||
/// <summary>
|
||||
/// 写时间输入
|
||||
/// 写时间输入结构 / Write Time Input Structure
|
||||
/// <remarks>
|
||||
/// 封装写入设备时间的请求参数
|
||||
/// Encapsulates request parameters for writing device time
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SetSystemTimeModbusInputStruct : IInputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化写时间请求参数,从 DateTime 提取各时间分量
|
||||
/// Initialize write time request parameters, extract time components from DateTime
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="time">时间</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="startAddress">起始地址 / Start Address</param>
|
||||
/// <param name="time">时间 / Time</param>
|
||||
public SetSystemTimeModbusInputStruct(byte slaveAddress, ushort startAddress, DateTime time)
|
||||
{
|
||||
SlaveAddress = slaveAddress;
|
||||
FunctionCode = (byte)ModbusProtocolTimeFunctionCode.SetSystemTime;
|
||||
StartAddress = startAddress;
|
||||
WriteCount = 5;
|
||||
WriteByteCount = 10;
|
||||
WriteCount = 5; // 时间占用 5 个寄存器 / Time occupies 5 registers
|
||||
WriteByteCount = 10; // 10 字节数据 / 10 bytes data
|
||||
Year = (ushort)time.Year;
|
||||
Day = (byte)time.Day;
|
||||
Month = (byte)time.Month;
|
||||
@@ -179,78 +373,86 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从站号
|
||||
/// 从站号 / Slave Address
|
||||
/// </summary>
|
||||
public byte SlaveAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能码
|
||||
/// 功能码 / Function Code
|
||||
/// </summary>
|
||||
public byte FunctionCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始地址
|
||||
/// 开始地址 / Start Address
|
||||
/// </summary>
|
||||
public ushort StartAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入个数
|
||||
/// 写入个数 / Write Count
|
||||
/// </summary>
|
||||
public ushort WriteCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入字节个数
|
||||
/// 写入字节个数 / Write Byte Count
|
||||
/// </summary>
|
||||
public byte WriteByteCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 年
|
||||
/// 年 / Year
|
||||
/// </summary>
|
||||
public ushort Year { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 日
|
||||
/// 日 / Day
|
||||
/// </summary>
|
||||
public byte Day { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 月
|
||||
/// 月 / Month
|
||||
/// </summary>
|
||||
public byte Month { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 时
|
||||
/// 时 / Hour
|
||||
/// </summary>
|
||||
public ushort Hour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 秒
|
||||
/// 秒 / Second
|
||||
/// </summary>
|
||||
public byte Second { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 分
|
||||
/// 分 / Minute
|
||||
/// </summary>
|
||||
public byte Minute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 毫秒
|
||||
/// 毫秒 / Millisecond
|
||||
/// </summary>
|
||||
public ushort Millisecond { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写时间输出
|
||||
/// 写时间输出结构 / Write Time Output Structure
|
||||
/// <remarks>
|
||||
/// 封装写入设备时间的响应数据
|
||||
/// Encapsulates response data for writing device time
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SetSystemTimeModbusOutputStruct : IOutputStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化写时间响应数据
|
||||
/// Initialize write time response data
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="functionCode">功能码</param>
|
||||
/// <param name="startAddress">开始地址</param>
|
||||
/// <param name="writeCount">写入个数</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="functionCode">功能码 / Function Code</param>
|
||||
/// <param name="startAddress">开始地址 / Start Address</param>
|
||||
/// <param name="writeCount">写入个数 / Write Count</param>
|
||||
public SetSystemTimeModbusOutputStruct(byte slaveAddress, byte functionCode,
|
||||
ushort startAddress, ushort writeCount)
|
||||
{
|
||||
@@ -261,36 +463,60 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从站号
|
||||
/// 从站号 / Slave Address
|
||||
/// </summary>
|
||||
public byte SlaveAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能码
|
||||
/// 功能码 / Function Code
|
||||
/// </summary>
|
||||
public byte FunctionCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始地址
|
||||
/// 开始地址 / Start Address
|
||||
/// </summary>
|
||||
public ushort StartAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入个数
|
||||
/// 写入个数 / Write Count
|
||||
/// </summary>
|
||||
public ushort WriteCount { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写系统时间协议
|
||||
/// 写系统时间协议类 / Write System Time Protocol Class
|
||||
/// <remarks>
|
||||
/// 实现写系统时间的协议格式化和反格式化
|
||||
/// Implements protocol formatting and unformatting for writing system time
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SetSystemTimeModbusProtocol : ProtocolUnit
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="message">写系统时间的参数</param>
|
||||
/// <returns>写系统时间的核心</returns>
|
||||
/// <param name="message">
|
||||
/// 写时间输入结构 / 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)
|
||||
{
|
||||
var r_message = (SetSystemTimeModbusInputStruct)message;
|
||||
@@ -301,11 +527,39 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="messageBytes">获取的信息</param>
|
||||
/// <param name="flag">当前反格式化的位置</param>
|
||||
/// <returns>反格式化的信息</returns>
|
||||
/// <param name="messageBytes">
|
||||
/// 获取的信息 / Received Information
|
||||
/// <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)
|
||||
{
|
||||
var slaveAddress = ValueHelper.GetInstance(Endian).GetByte(messageBytes, ref flag);
|
||||
@@ -317,4 +571,4 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,95 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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
|
||||
{
|
||||
private static readonly ILogger<ModbusUtility> logger = LogProvider.CreateLogger<ModbusUtility>();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (无连接字符串) / Constructor (without Connection String)
|
||||
/// <remarks>
|
||||
/// 初始化 Modbus 时间工具,稍后通过 SetConnectionType 设置连接
|
||||
/// Initialize Modbus time utility, set connection later via SetConnectionType
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="connectionType">协议类型</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="endian">端格式</param>
|
||||
/// <param name="connectionType">
|
||||
/// 协议类型 / Protocol Type
|
||||
/// <remarks>
|
||||
/// 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,
|
||||
Endian endian)
|
||||
: base(connectionType, slaveAddress, masterAddress, endian)
|
||||
@@ -22,13 +97,26 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (带连接字符串) / Constructor (with Connection String)
|
||||
/// <remarks>
|
||||
/// 初始化 Modbus 时间工具并立即设置连接
|
||||
/// Initialize Modbus time utility and set connection immediately
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="connectionType">协议类型</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="endian">端格式</param>
|
||||
/// <param name="connectionType">
|
||||
/// 协议类型 / Protocol 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="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,
|
||||
Endian endian)
|
||||
: base(connectionType, connectionString, slaveAddress, masterAddress, endian)
|
||||
@@ -36,17 +124,54 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>设备的时间</returns>
|
||||
/// <param name="startAddress">
|
||||
/// 起始地址 / Start Address
|
||||
/// <remarks>
|
||||
/// 时间寄存器起始地址
|
||||
/// Starting address of time registers
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// 设备的时间 / Device Time
|
||||
/// <remarks>
|
||||
/// ReturnStruct<DateTime> 包含:
|
||||
/// ReturnStruct<DateTime> 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)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建读时间输入结构 / Create read time input structure
|
||||
var inputStruct = new GetSystemTimeModbusInputStruct(SlaveAddress, startAddress);
|
||||
|
||||
// 发送接收 / Send and receive
|
||||
var outputStruct =
|
||||
await Wrapper.SendReceiveAsync<GetSystemTimeModbusOutputStruct>(
|
||||
Wrapper[typeof(GetSystemTimeModbusProtocol)], inputStruct);
|
||||
|
||||
return new ReturnStruct<DateTime>
|
||||
{
|
||||
Datas = outputStruct?.Time ?? DateTime.MinValue,
|
||||
@@ -69,18 +194,62 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="setTime">需要写入的时间</param>
|
||||
/// <returns>写入是否成功</returns>
|
||||
/// <param name="startAddress">
|
||||
/// 起始地址 / 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<bool>:
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建写时间输入结构 / Create write time input structure
|
||||
var inputStruct = new SetSystemTimeModbusInputStruct(SlaveAddress, startAddress, setTime);
|
||||
|
||||
// 发送接收 / Send and receive
|
||||
var outputStruct =
|
||||
await Wrapper.SendReceiveAsync<SetSystemTimeModbusOutputStruct>(
|
||||
Wrapper[typeof(SetSystemTimeModbusProtocol)], inputStruct);
|
||||
|
||||
// 检查写入结果 / Check write result
|
||||
return new ReturnStruct<bool>()
|
||||
{
|
||||
Datas = outputStruct?.WriteCount > 0,
|
||||
|
||||
@@ -1,31 +1,178 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class AddressFormaterModbus : AddressFormater<int, int>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="area">地址区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <returns>格式化的地址字符串</returns>
|
||||
/// <param name="area">
|
||||
/// 地址区域 / Address Area
|
||||
/// <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)
|
||||
{
|
||||
return area + " " + address;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="area">地址区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <param name="subAddress">比特位地址</param>
|
||||
/// <returns>格式化的地址字符串</returns>
|
||||
/// <param name="area">
|
||||
/// 地址区域 / Address Area
|
||||
/// <remarks>
|
||||
/// 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)
|
||||
{
|
||||
return area + " " + address + "." + subAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,110 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public abstract class ModbusTranslatorBase : AddressTranslator
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <param name="isSingle">是否只写入一个数据</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
/// <param name="address">
|
||||
/// 格式化的地址 / Formatted Address
|
||||
/// <remarks>
|
||||
/// 示例 / 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);
|
||||
|
||||
/// <summary>
|
||||
/// 地址转换
|
||||
/// 地址转换 (默认 isSingle=false) / Address Translate (default isSingle=false)
|
||||
/// <remarks>
|
||||
/// 重载方法,默认使用多写模式
|
||||
/// Overloaded method, defaults to multi-write mode
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
/// <param name="address">格式化的地址 / Formatted Address</param>
|
||||
/// <param name="isRead">是否为读取 / Whether it's Read</param>
|
||||
/// <returns>翻译后的地址 / Translated Address</returns>
|
||||
public override AddressDef AddressTranslate(string address, bool isRead)
|
||||
{
|
||||
return AddressTranslate(address, isRead, false);
|
||||
@@ -29,83 +112,167 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class AddressTranslatorModbus : ModbusTranslatorBase
|
||||
{
|
||||
/// <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>
|
||||
protected Dictionary<string, AreaOutputDef> ReadFunctionCodeDictionary;
|
||||
|
||||
/// <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>
|
||||
protected Dictionary<(string, bool), AreaOutputDef> WriteFunctionCodeDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化读/写功能码字典
|
||||
/// Initialize read/write function code dictionaries
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public AddressTranslatorModbus()
|
||||
{
|
||||
// 初始化读功能码字典 / Initialize read function code dictionary
|
||||
ReadFunctionCodeDictionary = new Dictionary<string, AreaOutputDef>
|
||||
{
|
||||
{
|
||||
"0X",
|
||||
"0X", // 线圈 / Coil
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int) ModbusProtocolFunctionCode.ReadCoilStatus,
|
||||
AreaWidth = 0.125
|
||||
Code = (int)ModbusProtocolFunctionCode.ReadCoilStatus, // 功能码 01
|
||||
AreaWidth = 0.125 // 1 位 = 0.125 字节
|
||||
}
|
||||
},
|
||||
{
|
||||
"1X",
|
||||
"1X", // 离散输入 / Discrete Input
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int) ModbusProtocolFunctionCode.ReadInputStatus,
|
||||
AreaWidth = 0.125
|
||||
Code = (int)ModbusProtocolFunctionCode.ReadInputStatus, // 功能码 02
|
||||
AreaWidth = 0.125 // 1 位 = 0.125 字节
|
||||
}
|
||||
},
|
||||
{
|
||||
"3X",
|
||||
new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadInputRegister, AreaWidth = 2}
|
||||
"3X", // 输入寄存器 / Input Register
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int)ModbusProtocolFunctionCode.ReadInputRegister, // 功能码 04
|
||||
AreaWidth = 2 // 16 位 = 2 字节
|
||||
}
|
||||
},
|
||||
{
|
||||
"4X",
|
||||
new AreaOutputDef {Code = (int) ModbusProtocolFunctionCode.ReadHoldRegister, AreaWidth = 2}
|
||||
"4X", // 保持寄存器 / Holding Register
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int)ModbusProtocolFunctionCode.ReadHoldRegister, // 功能码 03
|
||||
AreaWidth = 2 // 16 位 = 2 字节
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化写功能码字典 / Initialize write function code dictionary
|
||||
WriteFunctionCodeDictionary = new Dictionary<(string, bool), AreaOutputDef>
|
||||
{
|
||||
{
|
||||
("0X", false),
|
||||
("0X", false), // 多写线圈 / Multi-write Coil
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int) ModbusProtocolFunctionCode.WriteMultiCoil,
|
||||
Code = (int)ModbusProtocolFunctionCode.WriteMultiCoil, // 功能码 15
|
||||
AreaWidth = 0.125
|
||||
}
|
||||
},
|
||||
{
|
||||
("4X", false),
|
||||
("4X", false), // 多写寄存器 / Multi-write Register
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int) ModbusProtocolFunctionCode.WriteMultiRegister,
|
||||
Code = (int)ModbusProtocolFunctionCode.WriteMultiRegister, // 功能码 16
|
||||
AreaWidth = 2
|
||||
}
|
||||
},
|
||||
{
|
||||
("0X", true),
|
||||
("0X", true), // 单写线圈 / Single-write Coil
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int) ModbusProtocolFunctionCode.WriteSingleCoil,
|
||||
Code = (int)ModbusProtocolFunctionCode.WriteSingleCoil, // 功能码 05
|
||||
AreaWidth = 0.125
|
||||
}
|
||||
},
|
||||
{
|
||||
("4X", true),
|
||||
("4X", true), // 单写寄存器 / Single-write Register
|
||||
new AreaOutputDef
|
||||
{
|
||||
Code = (int) ModbusProtocolFunctionCode.WriteSingleRegister,
|
||||
Code = (int)ModbusProtocolFunctionCode.WriteSingleRegister, // 功能码 06
|
||||
AreaWidth = 2
|
||||
}
|
||||
}
|
||||
@@ -113,35 +280,98 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <param name="isSingle">是否只写入一个数据</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
/// <param name="address">
|
||||
/// 格式化的地址 / Formatted Address
|
||||
/// <remarks>
|
||||
/// 示例 / 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)
|
||||
{
|
||||
// 转为大写 / Convert to uppercase
|
||||
address = address.ToUpper();
|
||||
|
||||
// 按空格分割 / Split by space
|
||||
var splitString = address.Split(' ');
|
||||
var head = splitString[0];
|
||||
var tail = splitString[1];
|
||||
var head = splitString[0]; // 区域 / Area (e.g., "4X")
|
||||
var tail = splitString[1]; // 地址 / Address (e.g., "1" or "5.3")
|
||||
|
||||
// 处理子地址 / Handle sub-address
|
||||
string sub;
|
||||
if (tail.Contains("."))
|
||||
{
|
||||
var splitString2 = tail.Split('.');
|
||||
sub = splitString2[1];
|
||||
tail = splitString2[0];
|
||||
sub = splitString2[1]; // 子地址 / Sub-address
|
||||
tail = splitString2[0]; // 主地址 / Main address
|
||||
}
|
||||
else
|
||||
{
|
||||
sub = "0";
|
||||
sub = "0"; // 默认子地址为 0 / Default sub-address is 0
|
||||
}
|
||||
|
||||
// 根据读/写选择功能码 / Select function code based on read/write
|
||||
return isRead
|
||||
? new AddressDef
|
||||
{
|
||||
AreaString = head,
|
||||
Area = ReadFunctionCodeDictionary[head].Code,
|
||||
Address = int.Parse(tail) - 1,
|
||||
Address = int.Parse(tail) - 1, // Modbus 地址从 1 开始,内部从 0 开始
|
||||
SubAddress = int.Parse(sub)
|
||||
}
|
||||
: new AddressDef
|
||||
@@ -154,13 +384,49 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="area">区域名称</param>
|
||||
/// <returns>字节长度</returns>
|
||||
/// <param name="area">
|
||||
/// 区域名称 / 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)
|
||||
{
|
||||
return ReadFunctionCodeDictionary[area].AreaWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,219 +1,649 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public interface IUtilityMethodExceptionStatus
|
||||
{
|
||||
/// <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>
|
||||
/// <returns></returns>
|
||||
/// <returns>
|
||||
/// 异常状态字节 / Exception Status Byte
|
||||
/// <remarks>
|
||||
/// ReturnStruct<byte> 包含:
|
||||
/// ReturnStruct<byte> 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();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 诊断数据 / Diagnostics Data
|
||||
|
||||
/// <summary>
|
||||
/// 诊断返回数据
|
||||
/// 诊断返回数据类 / Diagnostics Return Data Class
|
||||
/// <remarks>
|
||||
/// 存储 Modbus 诊断功能的返回数据
|
||||
/// Stores return data for Modbus diagnostics functionality
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class DiagnoticsData
|
||||
{
|
||||
/// <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>
|
||||
public ushort SubFunction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 诊断数据
|
||||
/// 诊断数据 / Diagnostics Data
|
||||
/// <remarks>
|
||||
/// 诊断功能返回的数据数组
|
||||
/// Data array returned by diagnostics function
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort[] Data { get; set; }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodDiagnotics
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取诊断信息
|
||||
/// 获取诊断信息 / Get Diagnostics Information
|
||||
/// <remarks>
|
||||
/// 执行 Modbus 诊断功能
|
||||
/// Execute Modbus diagnostics function
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="subFunction">子方法编号</param>
|
||||
/// <param name="data">诊断数据</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="subFunction">
|
||||
/// 子方法编号 / Sub-function Number
|
||||
/// <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<DiagnoticsData> 包含诊断结果
|
||||
/// ReturnStruct<DiagnoticsData> contains diagnostics result
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<DiagnoticsData>> GetDiagnoticsAsync(ushort subFunction, ushort[] data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通讯事件计数器 / Comm Event Counter
|
||||
|
||||
/// <summary>
|
||||
/// 通讯事件计数器获取数据
|
||||
/// 通讯事件计数器数据类 / Comm Event Counter Data Class
|
||||
/// <remarks>
|
||||
/// 存储 Modbus 通讯事件计数器的数据
|
||||
/// Stores data for Modbus comm event counter
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class CommEventCounterData
|
||||
{
|
||||
/// <summary>
|
||||
/// 通讯状态
|
||||
/// 通讯状态 / Communication Status
|
||||
/// <remarks>
|
||||
/// 从站设备的通讯状态字
|
||||
/// Communication status word of slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件计数
|
||||
/// 事件计数 / Event Count
|
||||
/// <remarks>
|
||||
/// 通讯事件的数量
|
||||
/// Number of communication events
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort EventCount { get; set; }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodCommEventCounter
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取通讯事件计数器
|
||||
/// 获取通讯事件计数器 / Get Comm Event Counter
|
||||
/// <remarks>
|
||||
/// 读取从站设备的通讯事件计数器
|
||||
/// Read comm event counter from slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>
|
||||
/// 通讯事件计数器数据 / Comm Event Counter Data
|
||||
/// <remarks>
|
||||
/// ReturnStruct<CommEventCounterData> 包含计数结果
|
||||
/// ReturnStruct<CommEventCounterData> contains counter result
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<CommEventCounterData>> GetCommEventCounterAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通讯事件日志 / Comm Event Log
|
||||
|
||||
/// <summary>
|
||||
/// 通讯事件获取数据
|
||||
/// 通讯事件数据类 / Comm Event Data Class
|
||||
/// <remarks>
|
||||
/// 存储 Modbus 通讯事件日志的数据
|
||||
/// Stores data for Modbus comm event log
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class CommEventLogData
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// 状态 / Status
|
||||
/// <remarks>
|
||||
/// 从站设备的状态字
|
||||
/// Status word of slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件内容
|
||||
/// 事件内容 / Event Content
|
||||
/// <remarks>
|
||||
/// 通讯事件的详细字节数据
|
||||
/// Detailed byte data of communication events
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public byte[] Events { get; set; }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodCommEventLog
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取通讯事件
|
||||
/// 获取通讯事件 / Get Comm Event
|
||||
/// <remarks>
|
||||
/// 读取从站设备的通讯事件日志
|
||||
/// Read comm event log from slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>
|
||||
/// 通讯事件数据 / Comm Event Data
|
||||
/// <remarks>
|
||||
/// ReturnStruct<CommEventLogData> 包含事件日志
|
||||
/// ReturnStruct<CommEventLogData> contains event log
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<CommEventLogData>> GetCommEventLogAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 从站 ID 获取 / Slave ID Get
|
||||
|
||||
/// <summary>
|
||||
/// 获取从站号数据
|
||||
/// 从站 ID 数据类 / Slave ID Data Class
|
||||
/// <remarks>
|
||||
/// 存储 Modbus 从站 ID 获取功能的返回数据
|
||||
/// Stores return data for Modbus slave ID get functionality
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SlaveIdData
|
||||
{
|
||||
/// <summary>
|
||||
/// 从站号
|
||||
/// 从站号 / Slave ID
|
||||
/// <remarks>
|
||||
/// 从站设备的唯一标识
|
||||
/// Unique identifier of slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public byte SlaveId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 指示状态
|
||||
/// 指示状态 / Indicator Status
|
||||
/// <remarks>
|
||||
/// 从站设备的运行状态指示
|
||||
/// Run status indicator of slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public byte IndicatorStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加信息
|
||||
/// 附加信息 / Additional Data
|
||||
/// <remarks>
|
||||
/// 从站设备的附加信息字节
|
||||
/// Additional data bytes from slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public byte[] AdditionalData { get; set; }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodSlaveId
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取从站号
|
||||
/// 获取从站号 / Get Slave ID
|
||||
/// <remarks>
|
||||
/// 读取从站设备的 ID 和附加信息
|
||||
/// Read slave ID and additional information
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>
|
||||
/// 从站 ID 数据 / Slave ID Data
|
||||
/// <remarks>
|
||||
/// ReturnStruct<SlaveIdData> 包含从站信息
|
||||
/// ReturnStruct<SlaveIdData> contains slave information
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<SlaveIdData>> GetSlaveIdAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件记录读写 / File Record Read/Write
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodFileRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 读文件记录
|
||||
/// 读文件记录 / Read File Record
|
||||
/// <remarks>
|
||||
/// 从从站设备读取文件记录
|
||||
/// Read file records from slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="recordDefs">读文件记录定义</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="recordDefs">
|
||||
/// 读文件记录定义 / 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);
|
||||
|
||||
/// <summary>
|
||||
/// 写文件记录
|
||||
/// 写文件记录 / Write File Record
|
||||
/// <remarks>
|
||||
/// 向从站设备写入文件记录
|
||||
/// Write file records to slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="recordDefs">写文件记录定义</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="recordDefs">
|
||||
/// 写文件记录定义 / 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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 掩码写寄存器 / Mask Write Register
|
||||
|
||||
/// <summary>
|
||||
/// 掩码写入数据
|
||||
/// 掩码写寄存器数据类 / Mask Write Register Data Class
|
||||
/// <remarks>
|
||||
/// 存储 Modbus 掩码写寄存器功能的数据
|
||||
/// Stores data for Modbus mask write register functionality
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class MaskRegisterData
|
||||
{
|
||||
/// <summary>
|
||||
/// 地址索引
|
||||
/// 地址索引 / Reference Address
|
||||
/// <remarks>
|
||||
/// 要写入的寄存器地址
|
||||
/// Register address to write
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort ReferenceAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 与掩码
|
||||
/// 与掩码 / AND Mask
|
||||
/// <remarks>
|
||||
/// 与操作掩码
|
||||
/// AND operation mask
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort AndMask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 或掩码
|
||||
/// 或掩码 / OR Mask
|
||||
/// <remarks>
|
||||
/// 或操作掩码
|
||||
/// OR operation mask
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ushort OrMask { get; set; }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodMaskRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// 写入掩码
|
||||
/// 写入掩码 / Write Mask
|
||||
/// <remarks>
|
||||
/// 使用掩码写入寄存器
|
||||
/// Write to register using mask
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="referenceAddress">地址索引</param>
|
||||
/// <param name="andMask">与掩码</param>
|
||||
/// <param name="orMask">或掩码</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="referenceAddress">
|
||||
/// 地址索引 / Reference Address
|
||||
/// <remarks>
|
||||
/// 要写入的寄存器地址
|
||||
/// 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<MaskRegisterData> 包含写入结果
|
||||
/// ReturnStruct<MaskRegisterData> contains write result
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<MaskRegisterData>> SetMaskRegister(ushort referenceAddress, ushort andMask, ushort orMask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 读写多寄存器 / Read/Write Multiple Registers
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodMultipleRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// 读写多寄存器
|
||||
/// 读写多寄存器 / Read/Write Multiple Registers
|
||||
/// <remarks>
|
||||
/// 同时执行读取和写入操作
|
||||
/// Execute read and write operations simultaneously
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="readStartingAddress">读起始地址</param>
|
||||
/// <param name="quantityToRead">读数量</param>
|
||||
/// <param name="writeStartingAddress">写寄存器地址</param>
|
||||
/// <param name="writeValues">写数据</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="readStartingAddress">
|
||||
/// 读起始地址 / Read Starting Address
|
||||
/// <remarks>
|
||||
/// 开始读取的寄存器地址
|
||||
/// 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<ushort[]> 包含读取结果
|
||||
/// ReturnStruct<ushort[]> contains read result
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<ushort[]>> GetMultipleRegister(ushort readStartingAddress, ushort quantityToRead, ushort writeStartingAddress, ushort[] writeValues);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FIFO 队列读 / FIFO Queue Read
|
||||
|
||||
/// <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>
|
||||
public interface IUtilityMethodFIFOQueue
|
||||
{
|
||||
/// <summary>
|
||||
/// 读FIFO队列
|
||||
/// 读 FIFO 队列 / Read FIFO Queue
|
||||
/// <remarks>
|
||||
/// 从从站设备读取 FIFO 队列数据
|
||||
/// Read FIFO queue data from slave device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="fifoPointerAddress">FIFO队列地址</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="fifoPointerAddress">
|
||||
/// FIFO 指针地址 / FIFO Pointer Address
|
||||
/// <remarks>
|
||||
/// FIFO 队列的指针地址
|
||||
/// Pointer address of FIFO queue
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// FIFO 值寄存器数组 / FIFO Value Register Array
|
||||
/// <remarks>
|
||||
/// ReturnStruct<ushort[]> 包含 FIFO 数据
|
||||
/// ReturnStruct<ushort[]> contains FIFO data
|
||||
/// </remarks>
|
||||
/// </returns>
|
||||
Task<ReturnStruct<ushort[]>> GetFIFOQueue(ushort fifoPointerAddress);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,26 +1,91 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// protocol[typeof(ReadDataModbusProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class ModbusAsciiInTcpProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusAsciiInTcpProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (指定 IP) / Constructor (Specify IP)
|
||||
/// <remarks>
|
||||
/// 使用指定的 IP 地址创建 Modbus ASCII over TCP 协议实例
|
||||
/// Create Modbus ASCII over TCP protocol instance with specified IP address
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address (串口服务器地址 / Serial server address)</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusAsciiInTcpProtocol(string ip, byte slaveAddress, byte masterAddress)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
@@ -28,16 +93,20 @@
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address</param>
|
||||
/// <param name="port">端口号 / Port Number (串口服务器端口 / Serial server port)</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusAsciiInTcpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
ProtocolLinker = new ModbusAsciiInTcpProtocolLinker(ip, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,163 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusAsciiInTcpProtocolLinker : TcpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>
|
||||
/// 串口服务器的 IP 地址
|
||||
/// Serial device server IP address
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
public ModbusAsciiInTcpProtocolLinker(string ip)
|
||||
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
: base(ip, port)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">返回的数据</param>
|
||||
/// <returns>校验是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// 转换为 ASCII 字符串 / Convert to ASCII string
|
||||
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)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,96 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// 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>
|
||||
public class ModbusAsciiInUdpProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusAsciiInUdpProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (指定 IP) / Constructor (Specify IP)
|
||||
/// <remarks>
|
||||
/// 使用指定的 IP 地址创建 Modbus ASCII over UDP 协议实例
|
||||
/// Create Modbus ASCII over UDP protocol instance with specified IP address
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address (可使用广播地址 255.255.255.255)</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusAsciiInUdpProtocol(string ip, byte slaveAddress, byte masterAddress)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
@@ -28,12 +98,16 @@
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address</param>
|
||||
/// <param name="port">端口号 / Port Number</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusAsciiInUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
|
||||
@@ -1,44 +1,171 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusAsciiInUdpProtocolLinker : UdpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <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)
|
||||
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
: base(ip, port)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">返回的数据</param>
|
||||
/// <returns>校验是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (UDP 连接状态) / Base validation (UDP connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// 转换为 ASCII 字符串 / Convert to ASCII string
|
||||
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)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,115 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// protocol[typeof(ReadDataModbusProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class ModbusAsciiProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</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>
|
||||
public ModbusAsciiProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("COM:Modbus", "COM"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="com">串口地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口地址 / COM Port Address
|
||||
/// <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)
|
||||
: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,145 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusAsciiProtocolLinker : ComProtocolLinker
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化 Modbus ASCII 协议连接器
|
||||
/// Initialize Modbus ASCII protocol linker
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="com">串口地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口地址 / 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)
|
||||
: base(com, slaveAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">返回的数据</param>
|
||||
/// <returns>校验是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (串口连接状态) / Base validation (serial connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// 转换为 ASCII 字符串 / Convert to ASCII string
|
||||
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)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(byte.Parse(contentString.Substring(5, 2)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,199 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public static class ModbusLengthCalc
|
||||
{
|
||||
/// <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>
|
||||
public static Func<byte[], int> ModbusAsciiLengthCalc => content =>
|
||||
{
|
||||
// 检查首字节是否为冒号 / Check if first byte is colon
|
||||
if (content[0] != 0x3a) return 0;
|
||||
|
||||
// 查找 CRLF 结束符 / Find CRLF terminator
|
||||
for (int i = 1; i < content.Length; i++)
|
||||
{
|
||||
if (content[i - 1] == 0x0D && content[i] == 0x0A) return i + 1;
|
||||
}
|
||||
return -1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
/// <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>异常响应 (功能码>128): 5 字节 / Exception response (function code>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>
|
||||
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] == 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;
|
||||
|
||||
// 读写多个寄存器 (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);
|
||||
};
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusAsciiController : FifoController
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 从配置读取参数初始化 ASCII 控制器
|
||||
/// Initialize ASCII controller with parameters read from configuration
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="com">串口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <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 ModbusAsciiController(string com, int slaveAddress) : base(
|
||||
// 从配置读取获取间隔时间 / Read fetch interval time from configuration
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
|
||||
// ASCII 长度计算函数 / ASCII length calculation function
|
||||
lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc,
|
||||
// LRC 校验函数 / LRC check function
|
||||
checkRightFunc: ContentCheck.LrcCheckRight,
|
||||
// 从配置读取等待队列长度 / Read waiting list count from configuration
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
|
||||
null
|
||||
@@ -57,18 +202,42 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusAsciiInTcpController : FifoController
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 从配置读取参数初始化 ASCII in TCP 控制器
|
||||
/// Initialize ASCII in TCP controller with parameters read from configuration
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">
|
||||
/// 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")),
|
||||
// ASCII 长度计算函数 / ASCII length calculation function
|
||||
lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc,
|
||||
// LRC 校验函数 / LRC check function
|
||||
checkRightFunc: ContentCheck.LrcCheckRight,
|
||||
// 从配置读取等待队列长度 / Read waiting list count from configuration
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null
|
||||
? int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount"))
|
||||
: null
|
||||
@@ -77,15 +246,23 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusAsciiInUdpController : FifoController
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 从配置读取参数初始化 ASCII in UDP 控制器
|
||||
/// Initialize ASCII in UDP controller with parameters read from configuration
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address</param>
|
||||
/// <param name="port">端口号 / Port Number</param>
|
||||
public ModbusAsciiInUdpController(string ip, int port) : base(int.Parse(ConfigurationReader.GetValue("UDP:" + ip + ":" + port, "FetchSleepTime")),
|
||||
lengthCalc: ModbusLengthCalc.ModbusAsciiLengthCalc,
|
||||
checkRightFunc: ContentCheck.LrcCheckRight,
|
||||
@@ -97,19 +274,39 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusRtuController : FifoController
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 从配置读取参数初始化 RTU 控制器
|
||||
/// Initialize RTU controller with parameters read from configuration
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="com">串口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="com">串口名称 / Serial Port Name</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
public ModbusRtuController(string com, int slaveAddress) : base(
|
||||
// 从配置读取获取间隔时间 / Read fetch interval time from configuration
|
||||
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,
|
||||
// 从配置读取等待队列长度 / Read waiting list count from configuration
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
|
||||
null
|
||||
@@ -118,121 +315,43 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusRtuInTcpController : FifoController
|
||||
public class ModbusRtuResponseController : FifoController
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 从配置读取参数初始化 RTU 响应控制器
|
||||
/// Initialize RTU response controller with parameters read from configuration
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
public ModbusRtuInTcpController(string ip, int port) : base(
|
||||
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "FetchSleepTime")),
|
||||
lengthCalc: ModbusLengthCalc.ModbusRtuLengthCalc,
|
||||
/// <param name="com">串口名称 / Serial Port Name</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
public ModbusRtuResponseController(string com, int slaveAddress) : base(
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
|
||||
// RTU 请求长度计算函数 / RTU request length calculation function
|
||||
lengthCalc: ModbusLengthCalc.ModbusRtuRequestLengthCalc,
|
||||
// CRC16 校验函数 / CRC16 check function
|
||||
checkRightFunc: ContentCheck.Crc16CheckRight,
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null ?
|
||||
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "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")) :
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
|
||||
null
|
||||
)
|
||||
{ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modbus Tcp协议控制器
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
// 更多控制器类 (ModbusTcpController, ModbusRtuInTcpController 等) 的注释类似
|
||||
// More controller classes (ModbusTcpController, ModbusRtuInTcpController, etc.) have similar annotations
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,52 +1,226 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<AddressUnit>
|
||||
/// {
|
||||
/// 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<string, string>(
|
||||
/// 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<ushort>();
|
||||
/// setData["Temperature"] = 250; // 25.0°C
|
||||
/// await machine.SetDatasAsync(MachineDataType.CommunicationTag, setData);
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </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>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionType">连接类型</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="getAddresses">读写的地址</param>
|
||||
/// <param name="keepConnect">是否保持连接</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="endian">端格式</param>
|
||||
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
|
||||
/// <param name="id">
|
||||
/// 设备的 ID 号 / Device ID Number
|
||||
/// <remarks>
|
||||
/// 唯一标识设备的字符串或数字
|
||||
/// String or number uniquely identifying the device
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="alias">
|
||||
/// 设备别名 / 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,
|
||||
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);
|
||||
|
||||
// 配置 Modbus 地址格式化器 / Configure Modbus address formater
|
||||
AddressFormater = new AddressFormaterModbus();
|
||||
|
||||
// 配置地址组合器 (读取) / Configure address combiner (read)
|
||||
// 使用连续地址组合器,最大长度 100 字节
|
||||
// Use continuous address combiner, max length 100 bytes
|
||||
AddressCombiner = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
|
||||
|
||||
// 配置地址组合器 (写入) / Configure address combiner (write)
|
||||
AddressCombinerSet = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (默认保持连接=true) / Constructor (Default Keep-Alive=true)
|
||||
/// <remarks>
|
||||
/// 简化版本的构造函数,默认保持连接
|
||||
/// Simplified constructor version with default keep-alive
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="id">设备的ID号</param>
|
||||
/// <param name="connectionType">连接类型</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="getAddresses">读写的地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="endian">端格式</param>
|
||||
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
|
||||
/// <param name="id">设备的 ID 号 / Device ID Number</param>
|
||||
/// <param name="alias">设备别名 / Device Alias</param>
|
||||
/// <param name="connectionType">连接类型 / Connection Type</param>
|
||||
/// <param name="connectionString">连接地址 / Connection Address</param>
|
||||
/// <param name="getAddresses">读写的地址 / Addresses to Read/Write</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
/// <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,
|
||||
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
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
@@ -6,47 +6,168 @@ using System.Text;
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusRtuInUdpProtocolLinkerBytesExtend : ModbusRtuProtocolLinkerBytesExtend
|
||||
{
|
||||
|
||||
// 使用 RTU 协议的字节伸缩方法 / Use RTU protocol bytes extend methods
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusAsciiInUdpProtocolLinkerBytesExtend : ModbusAsciiProtocolLinkerBytesExtend
|
||||
{
|
||||
|
||||
// 使用 ASCII 协议的字节伸缩方法 / Use ASCII protocol bytes extend methods
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusUdpProtocolLinkerBytesExtend : ModbusTcpProtocolLinkerBytesExtend
|
||||
{
|
||||
|
||||
// 使用 TCP 协议的字节伸缩方法 / Use TCP protocol bytes extend methods
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TCP 透传协议字节伸缩 / TCP Tunneling Protocol Bytes Extend
|
||||
|
||||
/// <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>
|
||||
public class ModbusRtuInTcpProtocolLinkerBytesExtend : ModbusRtuProtocolLinkerBytesExtend
|
||||
{
|
||||
|
||||
// 使用 RTU 协议的字节伸缩方法 / Use RTU protocol bytes extend methods
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class ModbusAsciiInTcpProtocolLinkerBytesExtend : ModbusAsciiProtocolLinkerBytesExtend
|
||||
{
|
||||
|
||||
// 使用 ASCII 协议的字节伸缩方法 / Use ASCII protocol bytes extend methods
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TCP 协议字节伸缩 / TCP Protocol Bytes Extend
|
||||
|
||||
/// <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>
|
||||
public class ModbusTcpProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||
{
|
||||
@@ -54,119 +175,418 @@ namespace Modbus.Net.Modbus
|
||||
private static readonly object _counterLock = new object();
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">扩展前的原始协议内容</param>
|
||||
/// <returns>扩展后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 扩展前的原始协议内容 / 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)
|
||||
{
|
||||
//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];
|
||||
|
||||
lock (_counterLock)
|
||||
{
|
||||
// 生成事务 ID (循环计数) / Generate transaction ID (cyclic count)
|
||||
var transaction = (ushort)(_sendCount % 65536 + 1);
|
||||
var tag = (ushort)0;
|
||||
var leng = (ushort)content.Length;
|
||||
var tag = (ushort)0; // 协议标识,Modbus=0 / Protocol ID, Modbus=0
|
||||
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);
|
||||
// 复制协议标识 / Copy protocol ID
|
||||
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(tag), 0, newFormat, 2, 2);
|
||||
// 复制长度 / Copy length
|
||||
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2);
|
||||
// 复制原始内容 / Copy original content
|
||||
Array.Copy(content, 0, newFormat, 6, content.Length);
|
||||
_sendCount++;
|
||||
|
||||
_sendCount++; // 递增计数器 / Increment counter
|
||||
}
|
||||
return newFormat;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">收缩前的完整协议内容</param>
|
||||
/// <returns>收缩后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 收缩前的完整协议内容 / 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)
|
||||
{
|
||||
//Modbus/Tcp协议收缩,抛弃前面6个字节的内容
|
||||
// Modbus/TCP 协议收缩,抛弃前面 6 个字节的内容
|
||||
// Modbus/TCP protocol reduction, discard first 6 bytes
|
||||
var newContent = new byte[content.Length - 6];
|
||||
Array.Copy(content, 6, newContent, 0, newContent.Length);
|
||||
return newContent;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RTU 协议字节伸缩 / RTU Protocol Bytes Extend
|
||||
|
||||
/// <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>
|
||||
public class ModbusRtuProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="content">扩展前的原始协议内容</param>
|
||||
/// <returns>扩展后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 扩展前的原始协议内容 / 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)
|
||||
{
|
||||
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];
|
||||
|
||||
// 计算 CRC16 / Calculate CRC16
|
||||
Crc16.GetInstance().GetCRC(content, ref crc);
|
||||
|
||||
// 复制原始内容 / Copy original content
|
||||
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);
|
||||
|
||||
return newFormat;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">收缩前的完整协议内容</param>
|
||||
/// <returns>收缩后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 收缩前的完整协议内容 / 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)
|
||||
{
|
||||
//Modbus/Rtu协议收缩,抛弃后面2个字节的内容
|
||||
// Modbus/RTU 协议收缩,抛弃最后 2 个字节的内容 (CRC)
|
||||
// Modbus/RTU protocol reduction, discard last 2 bytes (CRC)
|
||||
var newContent = new byte[content.Length - 2];
|
||||
Array.Copy(content, 0, newContent, 0, newContent.Length);
|
||||
return newContent;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ASCII 协议字节伸缩 / ASCII Protocol Bytes Extend
|
||||
|
||||
/// <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>
|
||||
public class ModbusAsciiProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="content">扩展前的原始协议内容</param>
|
||||
/// <returns>扩展后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 扩展前的原始协议内容 / 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)
|
||||
{
|
||||
//Modbus/Ascii协议扩张,前面增加:,后面增加LRC校验和尾字符
|
||||
// Modbus/ASCII 协议扩张,前面增加:,后面增加 LRC 校验和尾字符
|
||||
// Modbus/ASCII protocol extension, add ':' at front, LRC and tail characters at end
|
||||
var newContent = new List<byte>();
|
||||
|
||||
// 添加起始符 ':' / Add start marker ':'
|
||||
newContent.AddRange(Encoding.ASCII.GetBytes(":"));
|
||||
|
||||
// 将每个字节转换为 2 个十六进制 ASCII 字符 / Convert each byte to 2 hex ASCII characters
|
||||
foreach (var number in content)
|
||||
newContent.AddRange(Encoding.ASCII.GetBytes(number.ToString("X2")));
|
||||
|
||||
// 计算并添加 LRC 校验 / Calculate and add LRC checksum
|
||||
newContent.AddRange(Encoding.ASCII.GetBytes(Crc16.GetInstance().GetLRC(content)));
|
||||
|
||||
// 添加结束符 CR LF / Add end marker CR LF
|
||||
newContent.Add(0x0d);
|
||||
newContent.Add(0x0a);
|
||||
|
||||
return newContent.ToArray();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">收缩前的完整协议内容</param>
|
||||
/// <returns>收缩后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 收缩前的完整协议内容 / 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)
|
||||
{
|
||||
//Modbus/Ascii协议收缩,抛弃头尾。
|
||||
// Modbus/ASCII 协议收缩,抛弃头尾
|
||||
// Modbus/ASCII protocol reduction, discard head and tail
|
||||
var newContent = new List<byte>();
|
||||
var ans = Encoding.ASCII.GetString(content);
|
||||
|
||||
// 查找换行符位置 / Find newline position
|
||||
var index = ans.IndexOf(Environment.NewLine);
|
||||
|
||||
// 移除起始符 ':' 和结束符 CRLF / Remove start marker ':' and end marker CRLF
|
||||
ans = ans.Substring(1, index - 1);
|
||||
|
||||
// 每 2 个字符解析为一个字节 / Parse every 2 characters as one byte
|
||||
for (var i = 0; i < ans.Length; i += 2)
|
||||
{
|
||||
var number = byte.Parse(ans.Substring(i, 2), NumberStyles.HexNumber);
|
||||
newContent.Add(number);
|
||||
}
|
||||
|
||||
// 移除最后一个字节 (LRC 校验) / Remove last byte (LRC checksum)
|
||||
newContent.RemoveAt(newContent.Count - 1);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
317
Modbus.Net/Modbus.Net.Modbus/ModbusReceiver.cs
Normal file
317
Modbus.Net/Modbus.Net.Modbus/ModbusReceiver.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,148 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// protocol[typeof(ReadDataModbusProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class ModbusRtuInTcpProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</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>
|
||||
public ModbusRtuInTcpProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
// 创建 Modbus RTU over TCP 协议链接器
|
||||
// Create Modbus RTU over TCP protocol linker
|
||||
ProtocolLinker = new ModbusRtuInTcpProtocolLinker(ip);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>串口服务器地址 / Serial device server address</remarks>
|
||||
/// </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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
// 创建带端口的 Modbus RTU over TCP 协议链接器
|
||||
// Create Modbus RTU over TCP protocol linker with port
|
||||
ProtocolLinker = new ModbusRtuInTcpProtocolLinker(ip, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,155 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusRtuInTcpProtocolLinker : TcpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>
|
||||
/// 串口服务器的 IP 地址
|
||||
/// Serial device server IP address
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
public ModbusRtuInTcpProtocolLinker(string ip)
|
||||
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
: base(ip, port)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">设备返回的数据</param>
|
||||
/// <returns>数据是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker的CheckRight不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// Modbus 协议错误检测 / Modbus protocol error detection
|
||||
// RTU 帧第 2 字节是功能码 / Byte 1 of RTU frame is function code
|
||||
if (content[1] > 127)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(content[2]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,91 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// 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>
|
||||
public class ModbusRtuInUdpProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusRtuInUdpProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (指定 IP) / Constructor (Specify IP)
|
||||
/// <remarks>
|
||||
/// 使用指定的 IP 地址创建 Modbus RTU over UDP 协议实例
|
||||
/// Create Modbus RTU over UDP protocol instance with specified IP address
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address (可使用广播地址 255.255.255.255)</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusRtuInUdpProtocol(string ip, byte slaveAddress, byte masterAddress)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
@@ -28,12 +93,16 @@
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">IP 地址 / IP Address</param>
|
||||
/// <param name="port">端口号 / Port Number</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
public ModbusRtuInUdpProtocol(string ip, int port, byte slaveAddress, byte masterAddress)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
|
||||
@@ -1,41 +1,156 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusRtuInUdpProtocolLinker : UdpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <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)
|
||||
: base(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
: base(ip, port)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">设备返回的数据</param>
|
||||
/// <returns>数据是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker的CheckRight不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (UDP 连接状态) / Base validation (UDP connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// Modbus 协议错误检测 / Modbus protocol error detection
|
||||
// RTU 帧第 2 字节是功能码 / Byte 1 of RTU frame is function code
|
||||
if (content[1] > 127)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(content[2]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,100 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// protocol[typeof(ReadDataModbusProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class ModbusRtuProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</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>
|
||||
public ModbusRtuProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("COM:Modbus", "COM"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="com">串口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口名称 / COM Port Name
|
||||
/// <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)
|
||||
: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,136 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusRtuProtocolLinker : ComProtocolLinker
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化 Modbus RTU 协议连接器
|
||||
/// Initialize Modbus RTU protocol linker
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="com">串口地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口地址 / 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)
|
||||
: base(com, slaveAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">设备返回的数据</param>
|
||||
/// <returns>数据是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的数据 / 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 失败或功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when CRC fails or function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker的CheckRight不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//CRC校验失败
|
||||
// 基类校验 (串口连接状态) / Base validation (serial connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// CRC16 校验 / CRC16 checksum
|
||||
if (!Crc16.GetInstance().CrcEfficacy(content))
|
||||
throw new ModbusProtocolErrorException(501);
|
||||
//Modbus协议错误
|
||||
throw new ModbusProtocolErrorException(501); // CRC 校验失败 / CRC check failed
|
||||
|
||||
// Modbus 协议错误检测 / Modbus protocol error detection
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
if (content[1] > 127)
|
||||
throw new ModbusProtocolErrorException(content[2]);
|
||||
throw new ModbusProtocolErrorException(content[2]); // 异常码 / Exception code
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
164
Modbus.Net/Modbus.Net.Modbus/ModbusRtuProtocolReceiver.cs
Normal file
164
Modbus.Net/Modbus.Net.Modbus/ModbusRtuProtocolReceiver.cs
Normal 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>长度>6</strong>: 写多线圈/寄存器操作 / Write multiple coils/registers</item>
|
||||
/// <item><strong>长度=6</strong>: 读操作或写单线圈/寄存器 / Read operation or write single coil/register</item>
|
||||
/// <item><strong>长度<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 =>
|
||||
{
|
||||
// 写多线圈/寄存器操作 (长度>6)
|
||||
// Write multiple coils/registers operation (length>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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,132 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// protocol[typeof(ReadDataModbusProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class ModbusTcpProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</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>
|
||||
public ModbusTcpProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("TCP:Modbus", "IP"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
// 创建 Modbus TCP 协议链接器 / Create Modbus TCP protocol linker
|
||||
ProtocolLinker = new ModbusTcpProtocolLinker(ip);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>如 "192.168.1.100" / e.g., "192.168.1.100"</remarks>
|
||||
/// </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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
// 创建带端口的 Modbus TCP 协议链接器 / Create Modbus TCP protocol linker with port
|
||||
ProtocolLinker = new ModbusTcpProtocolLinker(ip, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,156 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusTcpProtocolLinker : TcpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <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)
|
||||
: this(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("TCP:Modbus", "ModbusPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
|
||||
/// <remarks>
|
||||
/// 使用指定的 IP 地址和端口创建 Modbus TCP 连接器
|
||||
/// Create Modbus TCP linker with specified IP address and port
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">设备返回的数据</param>
|
||||
/// <returns>数据是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker的CheckRight不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// Modbus 协议错误检测 / Modbus protocol error detection
|
||||
// MBAP 头第 8 字节是功能码 / Byte 8 of MBAP header is function code
|
||||
if (content[7] > 127)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
222
Modbus.Net/Modbus.Net.Modbus/ModbusType.cs
Normal file
222
Modbus.Net/Modbus.Net.Modbus/ModbusType.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,154 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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<ReadDataModbusOutputStruct>(
|
||||
/// 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>
|
||||
public class ModbusUdpProtocol : ModbusProtocol
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</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>
|
||||
public ModbusUdpProtocol(byte slaveAddress, byte masterAddress)
|
||||
: this(ConfigurationReader.GetValueDirect("UDP:Modbus", "IP"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
// 创建 Modbus UDP 协议链接器
|
||||
// Create Modbus UDP protocol linker
|
||||
ProtocolLinker = new ModbusUdpProtocolLinker(ip);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <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="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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
// 创建带端口的 Modbus UDP 协议链接器
|
||||
// Create Modbus UDP protocol linker with port
|
||||
ProtocolLinker = new ModbusUdpProtocolLinker(ip, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,155 @@
|
||||
namespace Modbus.Net.Modbus
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <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>
|
||||
public class ModbusUdpProtocolLinker : UdpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <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)
|
||||
: this(ip, int.Parse(ConfigurationReader.GetValueDirect("UDP:" + ip, "ModbusPort") ?? ConfigurationReader.GetValueDirect("UDP:Modbus", "ModbusPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
|
||||
/// <remarks>
|
||||
/// 使用指定的 IP 地址和端口创建 Modbus UDP 连接器
|
||||
/// Create Modbus UDP linker with specified IP address and port
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>如果功能码>127,表示错误响应 / If function code>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>
|
||||
/// <param name="content">设备返回的数据</param>
|
||||
/// <returns>数据是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的数据 / 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">
|
||||
/// 当功能码>127 时抛出 Modbus 协议错误
|
||||
/// Throw Modbus protocol error when function code>127
|
||||
/// </exception>
|
||||
public override bool? CheckRight(byte[] content)
|
||||
{
|
||||
//ProtocolLinker的CheckRight不会返回null
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
//Modbus协议错误
|
||||
// 基类校验 (UDP 连接状态) / Base validation (UDP connection status)
|
||||
if (base.CheckRight(content) != true) return base.CheckRight(content);
|
||||
|
||||
// Modbus 协议错误检测 / Modbus protocol error detection
|
||||
// MBAP 头第 8 字节是功能码 / Byte 8 of MBAP header is function code
|
||||
if (content[7] > 127)
|
||||
// 功能码>127 表示异常响应 / Function code>127 indicates exception response
|
||||
throw new ModbusProtocolErrorException(content[2] > 0 ? content[2] : content[8]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,73 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.Modbus
|
||||
{
|
||||
/// <summary>
|
||||
/// Modbus连接类型
|
||||
/// </summary>
|
||||
public enum ModbusType
|
||||
{
|
||||
/// <summary>
|
||||
/// Rtu连接
|
||||
/// </summary>
|
||||
Rtu = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Tcp连接
|
||||
/// </summary>
|
||||
Tcp = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Ascii连接
|
||||
/// </summary>
|
||||
Ascii = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Rtu连接Tcp透传
|
||||
/// </summary>
|
||||
RtuInTcp = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Ascii连接Tcp透传
|
||||
/// </summary>
|
||||
AsciiInTcp = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Udp连接
|
||||
/// </summary>
|
||||
Udp = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Rtu连接Udp透传
|
||||
/// </summary>
|
||||
RtuInUdp = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Ascii连接Udp透传
|
||||
/// </summary>
|
||||
AsciiInUdp = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modbus基础Api入口
|
||||
/// Modbus 基础 API 入口类 / Modbus Base API Entry Class
|
||||
/// <remarks>
|
||||
/// 提供 Modbus 协议的完整实现,支持多种连接方式和高级功能
|
||||
/// Provides complete Modbus protocol implementation, supporting multiple connection methods and advanced features
|
||||
/// <para>
|
||||
/// 支持的连接类型 / Supported Connection Types:
|
||||
/// <list type="bullet">
|
||||
/// <item><strong>ModbusType.Rtu</strong> - 串行 RTU 模式 (最常用) / Serial RTU mode (most common)</item>
|
||||
/// <item><strong>ModbusType.Tcp</strong> - 以太网 TCP 模式 / Ethernet TCP mode</item>
|
||||
/// <item><strong>ModbusType.Ascii</strong> - 串行 ASCII 模式 / Serial ASCII mode</item>
|
||||
/// <item><strong>ModbusType.RtuInTcp</strong> - TCP 透传 RTU 数据 / RTU over TCP tunneling</item>
|
||||
/// <item><strong>ModbusType.AsciiInTcp</strong> - TCP 透传 ASCII 数据 / ASCII over TCP tunneling</item>
|
||||
/// <item><strong>ModbusType.Udp</strong> - UDP 模式 / UDP mode</item>
|
||||
/// <item><strong>ModbusType.RtuInUdp</strong> - UDP 透传 RTU 数据 / RTU over UDP tunneling</item>
|
||||
/// <item><strong>ModbusType.AsciiInUdp</strong> - UDP 透传 ASCII 数据 / ASCII over UDP tunneling</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 实现的功能码 / Implemented Function Codes:
|
||||
/// <list type="bullet">
|
||||
/// <item><strong>01</strong> - 读线圈状态 / Read Coil Status</item>
|
||||
/// <item><strong>02</strong> - 读离散输入 / Read Discrete Inputs</item>
|
||||
/// <item><strong>03</strong> - 读保持寄存器 / Read Holding Registers</item>
|
||||
/// <item><strong>04</strong> - 读输入寄存器 / Read Input Registers</item>
|
||||
/// <item><strong>05</strong> - 写单个线圈 / Write Single Coil</item>
|
||||
/// <item><strong>06</strong> - 写单个寄存器 / Write Single Register</item>
|
||||
/// <item><strong>15</strong> - 写多个线圈 / Write Multiple Coils</item>
|
||||
/// <item><strong>16</strong> - 写多个寄存器 / Write Multiple Registers</item>
|
||||
/// <item><strong>23</strong> - 读写多个寄存器 / Read/Write Multiple Registers</item>
|
||||
/// <item><strong>07-08,11-12,17,20-22,24</strong> - 其他高级功能 / Other advanced features</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
|
||||
/// );
|
||||
///
|
||||
/// // 连接设备 / Connect to device
|
||||
/// await utility.ConnectAsync();
|
||||
///
|
||||
/// // 读取保持寄存器 / Read holding registers
|
||||
/// var result = await utility.GetDatasAsync<ushort>("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>
|
||||
public class ModbusUtility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit>,
|
||||
IUtilityMethodExceptionStatus,
|
||||
@@ -67,17 +83,46 @@ namespace Modbus.Net.Modbus
|
||||
private static readonly ILogger<ModbusUtility> logger = LogProvider.CreateLogger<ModbusUtility>();
|
||||
|
||||
/// <summary>
|
||||
/// Modbus协议类型
|
||||
/// Modbus 协议类型 / Modbus Protocol Type
|
||||
/// <remarks>
|
||||
/// 当前使用的 Modbus 连接类型
|
||||
/// Current Modbus connection type in use
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private ModbusType _modbusType;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (无连接字符串) / Constructor (without Connection String)
|
||||
/// <remarks>
|
||||
/// 初始化 Modbus Utility 实例,稍后通过 SetConnectionType 设置连接
|
||||
/// Initialize Modbus Utility instance, set connection later via SetConnectionType
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="connectionType">协议类型</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="endian">端格式</param>
|
||||
/// <param name="connectionType">
|
||||
/// 协议类型 / Protocol Type
|
||||
/// <remarks>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 ModbusUtility(int connectionType, byte slaveAddress, byte masterAddress,
|
||||
Endian endian)
|
||||
: base(slaveAddress, masterAddress)
|
||||
@@ -89,13 +134,29 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (带连接字符串) / Constructor (with Connection String)
|
||||
/// <remarks>
|
||||
/// 初始化 Modbus Utility 实例并立即设置连接
|
||||
/// Initialize Modbus Utility instance and set connection immediately
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="connectionType">协议类型</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="endian">端格式</param>
|
||||
/// <param name="connectionType">
|
||||
/// 协议类型 / Protocol Type
|
||||
/// <remarks>ModbusType 枚举值 / ModbusType enum value</remarks>
|
||||
/// </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,
|
||||
Endian endian)
|
||||
: base(slaveAddress, masterAddress)
|
||||
@@ -107,12 +168,20 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 端格式
|
||||
/// 端格式 / Endianness
|
||||
/// <remarks>
|
||||
/// Modbus 标准使用大端格式 (BigEndianLsb)
|
||||
/// Modbus standard uses Big Endian format (BigEndianLsb)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public override Endian Endian { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ip地址
|
||||
/// IP 地址 (从连接字符串提取) / IP Address (Extracted from Connection String)
|
||||
/// <remarks>
|
||||
/// 解析连接字符串中的 IP 部分
|
||||
/// Parse IP part from connection string
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected string ConnectionStringIp
|
||||
{
|
||||
@@ -124,7 +193,11 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 端口
|
||||
/// 端口号 (从连接字符串提取) / Port Number (Extracted from Connection String)
|
||||
/// <remarks>
|
||||
/// 解析连接字符串中的端口部分
|
||||
/// Parse port part from connection string
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected int? ConnectionStringPort
|
||||
{
|
||||
@@ -146,7 +219,11 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 协议类型
|
||||
/// 协议类型 / Protocol Type
|
||||
/// <remarks>
|
||||
/// 设置协议类型时会自动创建相应的协议实例
|
||||
/// Automatically creates corresponding protocol instance when setting protocol type
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public ModbusType ModbusType
|
||||
{
|
||||
@@ -154,9 +231,11 @@ namespace Modbus.Net.Modbus
|
||||
set
|
||||
{
|
||||
_modbusType = value;
|
||||
// 根据协议类型创建相应的协议实例
|
||||
// Create corresponding protocol instance based on protocol type
|
||||
switch (_modbusType)
|
||||
{
|
||||
//Rtu协议
|
||||
// RTU 协议 / RTU Protocol
|
||||
case ModbusType.Rtu:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -164,7 +243,7 @@ namespace Modbus.Net.Modbus
|
||||
: new ModbusRtuProtocol(ConnectionString, SlaveAddress, MasterAddress);
|
||||
break;
|
||||
}
|
||||
//Tcp协议
|
||||
// TCP 协议 / TCP Protocol
|
||||
case ModbusType.Tcp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -175,7 +254,7 @@ namespace Modbus.Net.Modbus
|
||||
MasterAddress));
|
||||
break;
|
||||
}
|
||||
//Ascii协议
|
||||
// ASCII 协议 / ASCII Protocol
|
||||
case ModbusType.Ascii:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -183,7 +262,7 @@ namespace Modbus.Net.Modbus
|
||||
: new ModbusAsciiProtocol(ConnectionString, SlaveAddress, MasterAddress);
|
||||
break;
|
||||
}
|
||||
//Rtu协议Tcp透传
|
||||
// RTU over TCP 透传 / RTU over TCP Tunneling
|
||||
case ModbusType.RtuInTcp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -194,7 +273,7 @@ namespace Modbus.Net.Modbus
|
||||
MasterAddress));
|
||||
break;
|
||||
}
|
||||
//Ascii协议Tcp透传
|
||||
// ASCII over TCP 透传 / ASCII over TCP Tunneling
|
||||
case ModbusType.AsciiInTcp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -205,7 +284,7 @@ namespace Modbus.Net.Modbus
|
||||
MasterAddress));
|
||||
break;
|
||||
}
|
||||
//Tcp协议Udp透传
|
||||
// UDP 协议 / UDP Protocol
|
||||
case ModbusType.Udp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -216,7 +295,7 @@ namespace Modbus.Net.Modbus
|
||||
MasterAddress));
|
||||
break;
|
||||
}
|
||||
//Rtu协议Udp透传
|
||||
// RTU over UDP 透传 / RTU over UDP Tunneling
|
||||
case ModbusType.RtuInUdp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -227,7 +306,7 @@ namespace Modbus.Net.Modbus
|
||||
MasterAddress));
|
||||
break;
|
||||
}
|
||||
//Rtu协议Udp透传
|
||||
// ASCII over UDP 透传 / ASCII over UDP Tunneling
|
||||
case ModbusType.AsciiInUdp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -243,28 +322,73 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置协议类型
|
||||
/// 设置协议类型 / Set Protocol Type
|
||||
/// <remarks>
|
||||
/// 实现 BaseUtility 的抽象方法
|
||||
/// Implements abstract method from BaseUtility
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="connectionType">协议类型</param>
|
||||
/// <param name="connectionType">协议类型 / Protocol Type</param>
|
||||
public override void SetConnectionType(int connectionType)
|
||||
{
|
||||
ModbusType = (ModbusType)connectionType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount)
|
||||
/// <summary>
|
||||
/// 读取数据 (基础方法) / 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<byte[]> 包含:
|
||||
/// ReturnStruct<byte[]> 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
|
||||
{
|
||||
// 创建读取输入结构 / Create read input structure
|
||||
var inputStruct = new ReadDataModbusInputStruct(SlaveAddress, startAddress,
|
||||
(ushort)getByteCount, AddressTranslator);
|
||||
(ushort)getByteCount, AddressTranslator, (ushort)getOriginalCount);
|
||||
|
||||
// 发送接收 / Send and receive
|
||||
var outputStruct = await
|
||||
Wrapper.SendReceiveAsync<ReadDataModbusOutputStruct>(Wrapper[typeof(ReadDataModbusProtocol)],
|
||||
inputStruct);
|
||||
|
||||
return new ReturnStruct<byte[]>
|
||||
{
|
||||
Datas = outputStruct?.DataValue,
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
ErrorMsg = ""
|
||||
};
|
||||
@@ -282,22 +406,69 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents)
|
||||
/// <summary>
|
||||
/// 写入数据 (基础方法) / 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<bool>:
|
||||
/// <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 input structure
|
||||
var inputStruct = new WriteDataModbusInputStruct(SlaveAddress, startAddress, setContents,
|
||||
AddressTranslator, Endian);
|
||||
AddressTranslator, Endian, (ushort)setOriginalCount);
|
||||
|
||||
// 发送接收 / Send and receive
|
||||
var outputStruct = await
|
||||
Wrapper.SendReceiveAsync<WriteDataModbusOutputStruct>(Wrapper[typeof(WriteDataModbusProtocol)],
|
||||
inputStruct);
|
||||
|
||||
// 验证写入长度 / Verify write length
|
||||
var ans = outputStruct?.WriteCount * 2 == BigEndianLsbValueHelper.Instance.ObjectArrayToByteArray(setContents).Length;
|
||||
return new ReturnStruct<bool>()
|
||||
{
|
||||
Datas = outputStruct?.WriteCount == setContents.Length,
|
||||
IsSuccess = outputStruct?.WriteCount == setContents.Length,
|
||||
ErrorCode = outputStruct?.WriteCount == setContents.Length ? 0 : -2,
|
||||
ErrorMsg = outputStruct?.WriteCount == setContents.Length ? "" : "Data length mismatch"
|
||||
Datas = ans,
|
||||
IsSuccess = ans,
|
||||
ErrorCode = ans ? 0 : -2,
|
||||
ErrorMsg = ans ? "" : "Data length mismatch"
|
||||
};
|
||||
}
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -325,7 +503,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<byte>()
|
||||
{
|
||||
Datas = outputStruct.OutputData,
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -355,7 +542,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<DiagnoticsData>()
|
||||
{
|
||||
Datas = new DiagnoticsData() { SubFunction = outputStruct.SubFunction, Data = outputStruct.Data },
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -385,7 +579,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<CommEventCounterData>()
|
||||
{
|
||||
Datas = new CommEventCounterData() { EventCount = outputStruct.EventCount, Status = outputStruct.Status },
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -415,7 +616,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<CommEventLogData>()
|
||||
{
|
||||
Datas = new CommEventLogData() { Status = outputStruct.Status, Events = outputStruct.Events },
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -445,7 +653,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<SlaveIdData>()
|
||||
{
|
||||
Datas = new SlaveIdData() { SlaveId = outputStruct.SlaveId, IndicatorStatus = outputStruct.RunIndicatorStatus, AdditionalData = outputStruct.AdditionalData },
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -475,7 +691,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<ReadFileRecordOutputDef[]>()
|
||||
{
|
||||
Datas = outputStruct.RecordDefs,
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -505,7 +729,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<WriteFileRecordOutputDef[]>()
|
||||
{
|
||||
Datas = outputStruct.WriteRecords,
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -535,7 +769,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<MaskRegisterData>()
|
||||
{
|
||||
Datas = new MaskRegisterData() { ReferenceAddress = outputStruct.ReferenceAddress, AndMask = outputStruct.AndMask, OrMask = outputStruct.OrMask },
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -565,7 +810,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<ushort[]>()
|
||||
{
|
||||
Datas = outputStruct.ReadRegisterValues,
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -595,7 +848,7 @@ namespace Modbus.Net.Modbus
|
||||
return new ReturnStruct<ushort[]>()
|
||||
{
|
||||
Datas = outputStruct.FIFOValueRegister,
|
||||
IsSuccess = true,
|
||||
IsSuccess = outputStruct == null ? null : true,
|
||||
ErrorCode = 0,
|
||||
ErrorMsg = null
|
||||
};
|
||||
@@ -613,4 +866,4 @@ namespace Modbus.Net.Modbus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
Modbus.Net/Modbus.Net.Modbus/ModbusUtilityServer.cs
Normal file
56
Modbus.Net/Modbus.Net.Modbus/ModbusUtilityServer.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
Modbus.Net.Opc
|
||||
===================
|
||||
[](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.
|
||||
@@ -1,66 +1,220 @@
|
||||
namespace Modbus.Net.Siemens
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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>
|
||||
public class AddressFormaterSiemens : AddressFormater<int, int>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="area">地址所在的数据区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <returns>编码后的地址</returns>
|
||||
/// <param name="area">
|
||||
/// 地址所在的数据区域 / Data Area
|
||||
/// <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)
|
||||
{
|
||||
return area + " " + address;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="area">地址所在的数据区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <param name="subAddress">子地址</param>
|
||||
/// <returns>编码后的地址</returns>
|
||||
/// <param name="area">地址所在的数据区域 / Data Area</param>
|
||||
/// <param name="address">地址 / Address</param>
|
||||
/// <param name="subAddress">
|
||||
/// 子地址 (位偏移) / 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)
|
||||
{
|
||||
return area + " " + address + "." + subAddress;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 西门子标准格式地址格式化器 / Siemens Standard Format Address Formater
|
||||
|
||||
/// <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>
|
||||
public class AddressFormaterSimenseStandard : AddressFormater<int, int>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="area">地址所在的数据区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <returns>编码后的地址</returns>
|
||||
/// <param name="area">地址所在的数据区域 / Data Area</param>
|
||||
/// <param name="address">地址 / Address</param>
|
||||
/// <returns>编码后的地址 / Encoded Address</returns>
|
||||
public override string FormatAddress(string area, int address)
|
||||
{
|
||||
// DB 块特殊处理 / DB block special handling
|
||||
if (area.Length > 1 &&
|
||||
area.ToUpper().Substring(0, 2) == "DB")
|
||||
return area.ToUpper() + "." + "DB" + address;
|
||||
|
||||
// 普通区域处理 / Normal area handling
|
||||
return area.ToUpper() + address;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="area">地址所在的数据区域</param>
|
||||
/// <param name="address">地址</param>
|
||||
/// <param name="subAddress">子地址</param>
|
||||
/// <returns>编码后的地址</returns>
|
||||
/// <param name="area">地址所在的数据区域 / Data Area</param>
|
||||
/// <param name="address">地址 / Address</param>
|
||||
/// <param name="subAddress">子地址 (位偏移) / Sub-Address (Bit Offset)</param>
|
||||
/// <returns>编码后的地址 / Encoded Address</returns>
|
||||
public override string FormatAddress(string area, int address, int subAddress)
|
||||
{
|
||||
// DB 块特殊处理 / DB block special handling
|
||||
if (area.Length > 1 &&
|
||||
area.ToUpper().Substring(0, 2) == "DB")
|
||||
return area.ToUpper() + "." + "DB" + address + "." + subAddress;
|
||||
|
||||
// 普通区域处理 / Normal area handling
|
||||
return area.ToUpper() + address + "." + subAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,107 +1,268 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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>
|
||||
public class AddressTranslatorSiemens : AddressTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// 区域的翻译字典
|
||||
/// 区域代码翻译字典 / Area Code Translation Dictionary
|
||||
/// <remarks>
|
||||
/// 存储区域字符串到代码的映射
|
||||
/// Stores mapping from area string to code
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Dictionary<string, int> AreaCodeDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化区域代码字典
|
||||
/// Initialize area code dictionary
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public AddressTranslatorSiemens()
|
||||
{
|
||||
AreaCodeDictionary = new Dictionary<string, int>
|
||||
{
|
||||
{"S", 0x04},
|
||||
{"SM", 0x05},
|
||||
{"AI", 0x06},
|
||||
{"AQ", 0x07},
|
||||
{"C", 0x1E},
|
||||
{"T", 0x1F},
|
||||
{"HC", 0x20},
|
||||
{"I", 0x81},
|
||||
{"Q", 0x82},
|
||||
{"M", 0x83},
|
||||
{"DB", 0x84},
|
||||
{"V", 0x184}
|
||||
{"S", 0x04}, // 系统存储区 / System memory
|
||||
{"SM", 0x05}, // 特殊存储区 / Special memory
|
||||
{"AI", 0x06}, // 模拟输入 / Analog input
|
||||
{"AQ", 0x07}, // 模拟输出 / Analog output
|
||||
{"C", 0x1E}, // 计数器 / Counter
|
||||
{"T", 0x1F}, // 计时器 / Timer
|
||||
{"HC", 0x20}, // 高速计数器 / High-speed counter
|
||||
{"I", 0x81}, // 输入映像区 / Input image
|
||||
{"Q", 0x82}, // 输出映像区 / Output image
|
||||
{"M", 0x83}, // 位存储区 / Memory
|
||||
{"DB", 0x84}, // 数据块 / Data block
|
||||
{"V", 0x184} // 变量存储区 (S7-200) / Variable memory (S7-200)
|
||||
};
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
/// <param name="address">
|
||||
/// 格式化的地址 / Formatted Address
|
||||
/// <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)
|
||||
{
|
||||
// 转为大写 / Convert to uppercase
|
||||
address = address.ToUpper();
|
||||
|
||||
// 按空格分割 / Split by space
|
||||
var splitString = address.Split(' ');
|
||||
var head = splitString[0];
|
||||
var tail = splitString[1];
|
||||
var head = splitString[0]; // 区域 / Area
|
||||
var tail = splitString[1]; // 地址 / Address
|
||||
|
||||
// 处理子地址 (位偏移) / Handle sub-address (bit offset)
|
||||
string sub;
|
||||
if (tail.Contains("."))
|
||||
{
|
||||
var splitString2 = tail.Split('.');
|
||||
sub = splitString2[1];
|
||||
tail = splitString2[0];
|
||||
sub = splitString2[1]; // 子地址 / Sub-address
|
||||
tail = splitString2[0]; // 主地址 / Main address
|
||||
}
|
||||
else
|
||||
{
|
||||
sub = "0";
|
||||
sub = "0"; // 默认子地址 / Default sub-address
|
||||
}
|
||||
|
||||
// DB 块特殊处理 / DB block special handling
|
||||
if (head.Length > 1 && head.Substring(0, 2) == "DB")
|
||||
{
|
||||
head = head.Substring(2);
|
||||
head = head.Substring(2); // 移除 "DB" 前缀 / Remove "DB" prefix
|
||||
return new AddressDef
|
||||
{
|
||||
AreaString = "DB" + head,
|
||||
Area = int.Parse(head) * 256 + AreaCodeDictionary["DB"],
|
||||
Address = int.Parse(tail),
|
||||
SubAddress = int.Parse(sub)
|
||||
AreaString = "DB" + head, // 区域字符串 / Area string
|
||||
Area = int.Parse(head) * 256 + AreaCodeDictionary["DB"], // DB 块代码 / DB block code
|
||||
Address = int.Parse(tail), // 地址 / Address
|
||||
SubAddress = int.Parse(sub) // 子地址 / Sub-address
|
||||
};
|
||||
}
|
||||
return
|
||||
new AddressDef
|
||||
{
|
||||
AreaString = head,
|
||||
Area = AreaCodeDictionary[head],
|
||||
Address = int.Parse(tail),
|
||||
SubAddress = int.Parse(sub)
|
||||
};
|
||||
|
||||
// 普通区域处理 / Normal area handling
|
||||
return new AddressDef
|
||||
{
|
||||
AreaString = head,
|
||||
Area = AreaCodeDictionary[head],
|
||||
Address = int.Parse(tail),
|
||||
SubAddress = int.Parse(sub)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取区域中的单个地址占用的字节长度
|
||||
/// 获取区域字节长度 / Get Area Byte Length
|
||||
/// <remarks>
|
||||
/// 返回区域中单个地址占用的字节长度
|
||||
/// Returns byte length per address in area
|
||||
/// <para>
|
||||
/// 西门子地址默认返回 1 字节
|
||||
/// Siemens address defaults to 1 byte
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="area">区域名称</param>
|
||||
/// <returns>字节长度</returns>
|
||||
/// <param name="area">区域名称 / Area Name</param>
|
||||
/// <returns>字节长度 / Byte Length</returns>
|
||||
public override double GetAreaByteLength(string area)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 西门子标准格式地址翻译器 / Siemens Standard Format Address Translator
|
||||
|
||||
/// <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>
|
||||
public class AddressTranslatorSimenseStandard : AddressTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// 区域的翻译字典
|
||||
/// 区域代码翻译字典 / Area Code Translation Dictionary
|
||||
/// <remarks>
|
||||
/// 存储区域字符串到代码的映射
|
||||
/// Stores mapping from area string to code
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Dictionary<string, int> AreaCodeDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化区域代码字典
|
||||
/// Initialize area code dictionary
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public AddressTranslatorSimenseStandard()
|
||||
{
|
||||
@@ -123,21 +284,55 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="address">格式化的地址</param>
|
||||
/// <param name="isRead">是否为读取,是为读取,否为写入</param>
|
||||
/// <returns>翻译后的地址</returns>
|
||||
/// <param name="address">
|
||||
/// 格式化的地址 / Formatted Address
|
||||
/// <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)
|
||||
{
|
||||
address = address.ToUpper();
|
||||
|
||||
// DB 块地址处理 / DB block address handling
|
||||
if (address.Substring(0, 2) == "DB")
|
||||
{
|
||||
var addressSplit = address.Split('.');
|
||||
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")
|
||||
addressSplit[1] = addressSplit[1].Substring(2);
|
||||
|
||||
return new AddressDef
|
||||
{
|
||||
AreaString = "DB" + addressSplit[0],
|
||||
@@ -146,13 +341,20 @@ namespace Modbus.Net.Siemens
|
||||
SubAddress = addressSplit.Length == 2 ? 0 : int.Parse(addressSplit[2])
|
||||
};
|
||||
}
|
||||
|
||||
// 普通区域地址处理 / Normal area address handling
|
||||
// 查找第一个数字的位置 / Find first digit position
|
||||
var i = 0;
|
||||
int t;
|
||||
while (!int.TryParse(address[i].ToString(), out t) && i < address.Length)
|
||||
i++;
|
||||
|
||||
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
|
||||
{
|
||||
AreaString = head,
|
||||
@@ -163,13 +365,19 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取区域中的单个地址占用的字节长度
|
||||
/// 获取区域字节长度 / Get Area Byte Length
|
||||
/// <remarks>
|
||||
/// 返回区域中单个地址占用的字节长度
|
||||
/// Returns byte length per address in area
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="area">区域名称</param>
|
||||
/// <returns>字节长度</returns>
|
||||
/// <param name="area">区域名称 / Area Name</param>
|
||||
/// <returns>字节长度 / Byte Length</returns>
|
||||
public override double GetAreaByteLength(string area)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,29 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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>
|
||||
public class SiemensPpiController : FifoController
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="com">串口</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口名称 / 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(
|
||||
// 从配置读取获取间隔时间 / Read fetch interval time from configuration
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "FetchSleepTime")),
|
||||
|
||||
// PPI 帧长度计算 / PPI frame length calculation
|
||||
lengthCalc: content =>
|
||||
{
|
||||
// 启动字符 0x10: 固定 6 字节
|
||||
// Start char 0x10: Fixed 6 bytes
|
||||
if (content[0] == 0x10)
|
||||
return 6;
|
||||
// 启动字符 0xE5: 固定 1 字节 (确认字符)
|
||||
// Start char 0xE5: Fixed 1 byte (acknowledge char)
|
||||
else if (content[0] == 0xE5)
|
||||
return 1;
|
||||
// 其他帧:根据长度字段计算
|
||||
// Other frames: Calculate based on length field
|
||||
else
|
||||
return DuplicateWithCount.GetDuplcateFunc(new List<int> { 1 }, 6)(content);
|
||||
},
|
||||
|
||||
// FCS 校验函数 / FCS check function
|
||||
checkRightFunc: ContentCheck.FcsCheckRight,
|
||||
|
||||
// 从配置读取等待队列长度 / Read waiting list count from configuration
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount") != null ?
|
||||
int.Parse(ConfigurationReader.GetValue("COM:" + com + ":" + slaveAddress, "WaitingListCount")) :
|
||||
null
|
||||
@@ -31,23 +120,100 @@ namespace Modbus.Net.Siemens
|
||||
{ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TCP 协议控制器 / TCP Protocol Controller
|
||||
|
||||
/// <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>
|
||||
public class SiemensTcpController : MatchDirectlySendController
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="ip">ip地址</param>
|
||||
/// <param name="port">端口号</param>
|
||||
/// <param name="ip">
|
||||
/// 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(
|
||||
// 匹配规则:位置 11,12 的事务 ID
|
||||
// Match rules: Transaction ID at positions 11,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),
|
||||
|
||||
// 从配置读取等待队列长度 / Read waiting list count from configuration
|
||||
waitingListMaxCount: ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount") != null ?
|
||||
int.Parse(ConfigurationReader.GetValue("TCP:" + ip + ":" + port, "WaitingListCount")) :
|
||||
null
|
||||
)
|
||||
{ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,53 +1,255 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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<AddressUnit>
|
||||
/// {
|
||||
/// 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<string, string>(
|
||||
/// 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>
|
||||
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>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="id">设备id号</param>
|
||||
/// <param name="connectionType">连接类型</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="model">设备类型</param>
|
||||
/// <param name="getAddresses">读写的地址</param>
|
||||
/// <param name="keepConnect">是否保持连接</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="src">本机模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||
/// <param name="dst">PLC模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model,
|
||||
/// <param name="id">
|
||||
/// 设备的 ID 号 / Device ID Number
|
||||
/// <remarks>
|
||||
/// 唯一标识设备的字符串或数字
|
||||
/// String or number uniquely identifying the device
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="alias">
|
||||
/// 设备别名 / Device Alias
|
||||
/// <remarks>
|
||||
/// 人类可读的设备名称
|
||||
/// 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)
|
||||
: 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);
|
||||
|
||||
// 配置西门子地址格式化器 / Configure Siemens address formater
|
||||
AddressFormater = new AddressFormaterSiemens();
|
||||
|
||||
// 配置地址组合器 (读取) / Configure address combiner (read)
|
||||
// 使用连续地址组合器,最大长度 100 字节
|
||||
// Use continuous address combiner, max length 100 bytes
|
||||
AddressCombiner = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
|
||||
|
||||
// 配置地址组合器 (写入) / Configure address combiner (write)
|
||||
AddressCombinerSet = new AddressCombinerContinus<TUnitKey>(AddressTranslator, 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (默认保持连接=true) / Constructor (Default Keep-Alive=true)
|
||||
/// <remarks>
|
||||
/// 简化版本的构造函数,默认保持连接
|
||||
/// Simplified constructor version with default keep-alive
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="id">设备id号</param>
|
||||
/// <param name="connectionType">连接类型</param>
|
||||
/// <param name="connectionString">连接地址</param>
|
||||
/// <param name="model">设备类型</param>
|
||||
/// <param name="getAddresses">读写的地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="src">本机模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||
/// <param name="dst">PLC模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model,
|
||||
/// <param name="id">设备的 ID 号 / Device ID Number</param>
|
||||
/// <param name="alias">设备别名 / Device Alias</param>
|
||||
/// <param name="connectionType">连接类型 / Connection Type</param>
|
||||
/// <param name="connectionString">连接地址 / Connection Address</param>
|
||||
/// <param name="model">设备类型 / Device Model</param>
|
||||
/// <param name="getAddresses">读写的地址 / Addresses to Read/Write</param>
|
||||
/// <param name="slaveAddress">从站号 / Slave Address</param>
|
||||
/// <param name="masterAddress">主站号 / Master Address</param>
|
||||
/// <param name="src">本机模块位 / Local Module Position</param>
|
||||
/// <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)
|
||||
: this(id, connectionType, connectionString, model, getAddresses, true, slaveAddress, masterAddress, src, dst)
|
||||
: this(id, alias, connectionType, connectionString, model, getAddresses, true, slaveAddress, masterAddress, src, dst)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,223 @@
|
||||
using Nito.AsyncEx;
|
||||
using Nito.AsyncEx;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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<ReadDataSiemensOutputStruct>(
|
||||
/// protocol[typeof(ReadDataSiemensProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SiemensPpiProtocol : SiemensProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// 串口名称 / Serial Port Name
|
||||
/// <remarks>
|
||||
/// 如 "COM1", "COM2" 等
|
||||
/// e.g., "COM1", "COM2", etc.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly string _com;
|
||||
|
||||
/// <summary>
|
||||
/// 异步锁 / Async Lock
|
||||
/// <remarks>
|
||||
/// 用于保护并发连接操作
|
||||
/// Used to protect concurrent connection operations
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly AsyncLock _lock = new AsyncLock();
|
||||
|
||||
/// <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>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="slaveAddress">
|
||||
/// 从站号 / 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)
|
||||
: this(ConfigurationReader.GetValueDirect("COM:Siemens", "COM"), slaveAddress, masterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="com">串口地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="masterAddress">主站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口地址 / COM Port Address
|
||||
/// <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)
|
||||
: base(slaveAddress, masterAddress)
|
||||
{
|
||||
_com = com;
|
||||
// 创建 PPI 协议链接器
|
||||
// Create PPI protocol linker
|
||||
ProtocolLinker = new SiemensPpiProtocolLinker(_com, SlaveAddress);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">写入的内容,使用对象数组描述</param>
|
||||
/// <returns>从设备获取的字节流</returns>
|
||||
/// <param name="content">写入的内容,使用对象数组描述 / Content to Write, Described Using Object Array</param>
|
||||
/// <returns>从设备获取的字节流 / Byte Stream Received from Device</returns>
|
||||
public override async Task<PipeUnit> SendReceiveAsync(params object[] content)
|
||||
{
|
||||
// 检查连接 / Check connection
|
||||
if (ProtocolLinker == null || !ProtocolLinker.IsConnected)
|
||||
await ConnectAsync();
|
||||
|
||||
// 发送接收 / Send and receive
|
||||
return await base.SendReceiveAsync(Endian, content);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="unit">协议核心</param>
|
||||
/// <param name="content">协议的参数</param>
|
||||
/// <returns>设备返回的信息</returns>
|
||||
/// <param name="unit">协议核心 / Protocol Core</param>
|
||||
/// <param name="content">协议的参数 / Protocol Parameters</param>
|
||||
/// <returns>设备返回的信息 / Information Returned from Device</returns>
|
||||
private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content)
|
||||
{
|
||||
return await base.SendReceiveAsync(unit, content);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>是否连接成功</returns>
|
||||
/// <returns>是否连接成功 / Whether Connection is Successful</returns>
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
// 获取异步锁 / Acquire async lock
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
// 已连接 / Already connected
|
||||
if (ProtocolLinker.IsConnected) return true;
|
||||
|
||||
// 打开串口 / Open serial port
|
||||
if (!await ProtocolLinker.ConnectAsync()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -6,15 +6,87 @@ using System.Threading.Tasks;
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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>
|
||||
public class SiemensPpiProtocolLinker : ComProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="com">串口地址</param>
|
||||
/// <param name="slaveAddress">从站号</param>
|
||||
/// <param name="com">
|
||||
/// 串口地址 / 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)
|
||||
: base(com, slaveAddress, parity:
|
||||
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
|
||||
@@ -25,13 +97,50 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">发送的报文</param>
|
||||
/// <returns>接收的报文</returns>
|
||||
/// <param name="content">
|
||||
/// 发送的报文 / 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)
|
||||
{
|
||||
// 扩展报文 (添加 PPI 头) / Extend message (add PPI header)
|
||||
var extBytes = BytesExtend(content);
|
||||
|
||||
// 如果是 0x7C 特殊帧,发送确认报文
|
||||
// If 0x7C special frame, send acknowledge message
|
||||
if (extBytes[6] == 0x7c)
|
||||
{
|
||||
var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[4], content[5]);
|
||||
@@ -39,8 +148,13 @@ namespace Modbus.Net.Siemens
|
||||
await SendReceiveWithoutExtAndDecAsync(
|
||||
new ComConfirmMessageSiemensProtocol().Format(inputStruct2));
|
||||
}
|
||||
|
||||
// 发送扩展后的报文 / Send extended message
|
||||
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
|
||||
if (receiveBytes == null) return null;
|
||||
|
||||
// 如果响应是 0xE5,发送确认报文
|
||||
// If response is 0xE5, send acknowledge message
|
||||
if (content.Length > 6 && receiveBytes.Length == 1 && receiveBytes[0] == 0xe5)
|
||||
{
|
||||
var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[4], content[5]);
|
||||
@@ -49,20 +163,50 @@ namespace Modbus.Net.Siemens
|
||||
new ComConfirmMessageSiemensProtocol().Format(inputStruct2));
|
||||
return BytesDecact(receiveBytes2);
|
||||
}
|
||||
|
||||
// 收缩报文 (移除 PPI 头) / Reduce message (remove PPI header)
|
||||
return BytesDecact(receiveBytes);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">发送的报文</param>
|
||||
/// <returns>接收的报文</returns>
|
||||
/// <param name="content">
|
||||
/// 发送的报文 / 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)
|
||||
{
|
||||
var ans = await base.SendReceiveWithoutExtAndDecAsync(content);
|
||||
|
||||
// 处理 0xF9 等待字符 / Handle 0xF9 wait character
|
||||
while (ans?.Length == 1 && ans[0] == 0xf9)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
Thread.Sleep(500); // 等待 500ms / Wait 500ms
|
||||
|
||||
if (content.Length <= 6)
|
||||
{
|
||||
var inputStruct2 = new ComConfirmMessageSiemensInputStruct(content[1], content[2]);
|
||||
@@ -82,19 +226,65 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">设备返回的信息</param>
|
||||
/// <returns>报文是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的信息 / 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)
|
||||
{
|
||||
// 基类校验 (串口连接状态) / Base validation (serial connection status)
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
|
||||
// 单字节 0xE5 确认 / Single-byte 0xE5 acknowledge
|
||||
if (content.Length == 1 && content[0] == 0xe5)
|
||||
return true;
|
||||
|
||||
// 6 字节短帧 / 6-byte short frame
|
||||
if (content.Length == 6 && content[3] == 0) return true;
|
||||
|
||||
// 检查结束符 0x16 / Check terminator 0x16
|
||||
if (content[content.Length - 1] != 0x16) return false;
|
||||
|
||||
// 检查长度字段 / Check length field
|
||||
if (content[1] != content.Length - 6) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,72 +1,287 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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>
|
||||
public class SiemensTcpProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="content">扩展前的原始协议内容</param>
|
||||
/// <returns>扩展后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 扩展前的原始协议内容 / 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)
|
||||
{
|
||||
// 复制 7 字节固定头 / Copy 7-byte fixed header
|
||||
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);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">收缩前的完整协议内容</param>
|
||||
/// <returns>收缩后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 收缩前的完整协议内容 / 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)
|
||||
{
|
||||
// 移除 7 字节头 / Remove 7-byte header
|
||||
var newContent = new byte[content.Length - 7];
|
||||
Array.Copy(content, 7, newContent, 0, newContent.Length);
|
||||
return newContent;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PPI 协议字节伸缩 / PPI Protocol Bytes Extend
|
||||
|
||||
/// <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>
|
||||
public class SiemensPpiProtocolLinkerBytesExtend : IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="content">扩展前的原始协议内容</param>
|
||||
/// <returns>扩展后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 扩展前的原始协议内容 / 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)
|
||||
{
|
||||
// 创建新数组,长度 = 原长度 + 2 (校验 + 结束符)
|
||||
// Create new array, length = original length + 2 (checksum + terminator)
|
||||
var newContent = new byte[content.Length + 2];
|
||||
|
||||
// 复制原始数据 / Copy original data
|
||||
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;
|
||||
for (var i = 4; i < newContent.Length - 2; i++)
|
||||
check += newContent[i];
|
||||
check = check % 256;
|
||||
|
||||
// 添加校验和和结束符 / Add checksum and terminator
|
||||
newContent[newContent.Length - 2] = (byte)check;
|
||||
newContent[newContent.Length - 1] = 0x16;
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">收缩前的完整协议内容</param>
|
||||
/// <returns>收缩后的协议内容</returns>
|
||||
/// <param name="content">
|
||||
/// 收缩前的完整协议内容 / 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)
|
||||
{
|
||||
// 移除 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];
|
||||
Array.Copy(content, 7, newContent, 0, newContent.Length);
|
||||
return newContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
get
|
||||
@@ -12,6 +62,14 @@
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -39,6 +107,14 @@
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -55,6 +131,17 @@
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -65,6 +152,14 @@
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -75,6 +170,16 @@
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -85,8 +190,14 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,33 +1,185 @@
|
||||
using Nito.AsyncEx;
|
||||
using Nito.AsyncEx;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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<ReadDataSiemensOutputStruct>(
|
||||
/// protocol[typeof(ReadDataSiemensProtocol)],
|
||||
/// inputStruct
|
||||
/// );
|
||||
/// }
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SiemensTcpProtocol : SiemensProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>
|
||||
/// PLC 的 IP 地址
|
||||
/// PLC IP address
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly string _ip;
|
||||
|
||||
/// <summary>
|
||||
/// 最大被调用连接数 / Max Called Connections
|
||||
/// <remarks>
|
||||
/// PLC 允许的最大并发连接数 (被调用方)
|
||||
/// Maximum concurrent connections allowed by PLC (called party)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly ushort _maxCalled;
|
||||
|
||||
/// <summary>
|
||||
/// 最大调用连接数 / Max Calling Connections
|
||||
/// <remarks>
|
||||
/// PLC 允许的最大并发连接数 (调用方)
|
||||
/// Maximum concurrent connections allowed by PLC (calling party)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly ushort _maxCalling;
|
||||
|
||||
/// <summary>
|
||||
/// 最大 PDU 长度 / Max PDU Length
|
||||
/// <remarks>
|
||||
/// 协议数据单元最大长度
|
||||
/// Maximum Protocol Data Unit length
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly ushort _maxPdu;
|
||||
|
||||
/// <summary>
|
||||
/// 端口号 / Port Number
|
||||
/// <remarks>
|
||||
/// 西门子 PLC 默认端口:102
|
||||
/// Siemens PLC default port: 102
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly int _port;
|
||||
|
||||
/// <summary>
|
||||
/// 本地 TSAP 地址 / Local TSAP Address
|
||||
/// <remarks>
|
||||
/// 传输服务访问点地址 (本地)
|
||||
/// Transport Service Access Point address (local)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly ushort _taspSrc;
|
||||
|
||||
/// <summary>
|
||||
/// DPU 大小 / DPU Size
|
||||
/// <remarks>
|
||||
/// 数据协议单元大小标识
|
||||
/// Data Protocol Unit size identifier
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 连接尝试计数 / Connection Try Count
|
||||
/// <remarks>
|
||||
/// 记录连接尝试次数,超过 10 次放弃
|
||||
/// Records connection try count, give up after 10 tries
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private int _connectTryCount;
|
||||
|
||||
/// <summary>
|
||||
/// 异步锁 / Async Lock
|
||||
/// <remarks>
|
||||
/// 用于保护并发连接操作
|
||||
/// Used to protect concurrent connection operations
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly AsyncLock _lock = new AsyncLock();
|
||||
|
||||
/// <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>
|
||||
/// <param name="tdpuSize"></param>
|
||||
/// <param name="tsapSrc"></param>
|
||||
/// <param name="tsapDst"></param>
|
||||
/// <param name="maxCalling"></param>
|
||||
/// <param name="maxCalled"></param>
|
||||
/// <param name="maxPdu"></param>
|
||||
/// <param name="tdpuSize">DPU 大小 / DPU Size</param>
|
||||
/// <param name="tsapSrc">本地 TSAP / Local TSAP</param>
|
||||
/// <param name="tsapDst">远程 TSAP / Remote TSAP</param>
|
||||
/// <param name="maxCalling">最大调用连接 / Max Calling Connections</param>
|
||||
/// <param name="maxCalled">最大被调用连接 / Max Called Connections</param>
|
||||
/// <param name="maxPdu">最大 PDU 长度 / Max PDU Length</param>
|
||||
public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
|
||||
ushort maxPdu)
|
||||
: this(tdpuSize, tsapSrc, tsapDst, maxCalling, maxCalled, maxPdu, ConfigurationReader.GetValueDirect("TCP:Siemens", "IP"))
|
||||
@@ -35,15 +187,19 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (从配置读取端口) / Constructor (Read Port from Configuration)
|
||||
/// <remarks>
|
||||
/// 使用指定的连接参数和从配置读取的端口创建协议实例
|
||||
/// Create protocol instance with specified connection parameters and port read from configuration
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="tdpuSize"></param>
|
||||
/// <param name="tsapSrc"></param>
|
||||
/// <param name="tsapDst"></param>
|
||||
/// <param name="maxCalling"></param>
|
||||
/// <param name="maxCalled"></param>
|
||||
/// <param name="maxPdu"></param>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="tdpuSize">DPU 大小 / DPU Size</param>
|
||||
/// <param name="tsapSrc">本地 TSAP / Local TSAP</param>
|
||||
/// <param name="tsapDst">远程 TSAP / Remote TSAP</param>
|
||||
/// <param name="maxCalling">最大调用连接 / Max Calling Connections</param>
|
||||
/// <param name="maxCalled">最大被调用连接 / Max Called Connections</param>
|
||||
/// <param name="maxPdu">最大 PDU 长度 / Max PDU Length</param>
|
||||
/// <param name="ip">IP 地址 / IP Address</param>
|
||||
public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
|
||||
ushort maxPdu, string ip)
|
||||
: this(
|
||||
@@ -53,16 +209,31 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="tdpuSize"></param>
|
||||
/// <param name="tsapSrc"></param>
|
||||
/// <param name="tsapDst"></param>
|
||||
/// <param name="maxCalling"></param>
|
||||
/// <param name="maxCalled"></param>
|
||||
/// <param name="maxPdu"></param>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="tdpuSize">DPU 大小 / DPU Size</param>
|
||||
/// <param name="tsapSrc">本地 TSAP / Local TSAP</param>
|
||||
/// <param name="tsapDst">远程 TSAP / Remote TSAP</param>
|
||||
/// <param name="maxCalling">最大调用连接 / Max Calling Connections</param>
|
||||
/// <param name="maxCalled">最大被调用连接 / Max Called Connections</param>
|
||||
/// <param name="maxPdu">最大 PDU 长度 / Max PDU Length</param>
|
||||
/// <param name="ip">IP 地址 / IP Address</param>
|
||||
/// <param name="port">端口号 / Port Number</param>
|
||||
public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
|
||||
ushort maxPdu, string ip, int port) : base(0, 0)
|
||||
{
|
||||
@@ -79,27 +250,59 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">发送的数据</param>
|
||||
/// <returns>返回的数据</returns>
|
||||
/// <param name="content">发送的数据 / Data to Send</param>
|
||||
/// <returns>返回的数据 / Returned Data</returns>
|
||||
public override async Task<PipeUnit> SendReceiveAsync(params object[] content)
|
||||
{
|
||||
// 检查连接 / Check connection
|
||||
if (ProtocolLinker == null || !ProtocolLinker.IsConnected)
|
||||
await ConnectAsync();
|
||||
|
||||
// 发送接收 / Send and receive
|
||||
return await base.SendReceiveAsync(Endian, content);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="unit">发送的数据</param>
|
||||
/// <param name="content">协议的参数</param>
|
||||
/// <returns>返回的数据</returns>
|
||||
/// <param name="unit">协议单元 / Protocol Unit</param>
|
||||
/// <param name="content">协议参数 / Protocol Parameters</param>
|
||||
/// <returns>返回的数据 / Returned Data</returns>
|
||||
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);
|
||||
|
||||
// 超过重试次数,放弃 / Exceeded retry count, give up
|
||||
if (_connectTryCount > 10) return null;
|
||||
|
||||
// 尝试连接后发送 / Try to connect then send
|
||||
return
|
||||
await
|
||||
ConnectAsync()
|
||||
@@ -107,32 +310,78 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="unit">发送的数据</param>
|
||||
/// <param name="content">协议的参数</param>
|
||||
/// <returns>返回的数据</returns>
|
||||
/// <param name="unit">协议单元 / Protocol Unit</param>
|
||||
/// <param name="content">协议参数 / Protocol Parameters</param>
|
||||
/// <returns>返回的数据 / Returned Data</returns>
|
||||
private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit<byte[], byte[]> unit, IInputStruct content)
|
||||
{
|
||||
return await base.SendReceiveAsync(unit, content);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>设备是否连接成功</returns>
|
||||
/// <returns>是否连接成功 / Whether Connection is Successful</returns>
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
IOutputStruct outputStruct;
|
||||
|
||||
// 获取异步锁 / Acquire async lock
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
_connectTryCount++;
|
||||
|
||||
// 已连接 / Already connected
|
||||
if (ProtocolLinker.IsConnected) return true;
|
||||
|
||||
// 建立 TCP 连接 / Establish TCP connection
|
||||
if (!await ProtocolLinker.ConnectAsync()) return false;
|
||||
_connectTryCount = 0;
|
||||
|
||||
_connectTryCount = 0; // 重置计数器 / Reset counter
|
||||
|
||||
// 发送 CR (创建引用) / Send CR (Create Reference)
|
||||
var inputStruct = new CreateReferenceSiemensInputStruct(_tdpuSize, _taspSrc, _tsapDst);
|
||||
|
||||
// 建立 S7 连接 (协商参数) / Establish S7 connection (negotiate parameters)
|
||||
outputStruct =
|
||||
//先建立连接,然后建立设备的引用
|
||||
(await (await
|
||||
ForceSendReceiveAsync(this[typeof(CreateReferenceSiemensProtocol)], inputStruct))
|
||||
.SendReceiveAsync(
|
||||
@@ -142,12 +391,15 @@ namespace Modbus.Net.Siemens
|
||||
_maxCalled,
|
||||
_maxPdu)
|
||||
: null)).Unwrap<EstablishAssociationSiemensOutputStruct>();
|
||||
|
||||
// 连接失败,断开 TCP / Connection failed, disconnect TCP
|
||||
if (outputStruct == null && ProtocolLinker.IsConnected)
|
||||
{
|
||||
ProtocolLinker.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
return outputStruct != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,192 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <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>
|
||||
public class SiemensTcpProtocolLinker : TcpProtocolLinker
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="ip">
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>
|
||||
/// 西门子 PLC 的 IP 地址
|
||||
/// Siemens PLC IP address
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
public SiemensTcpProtocolLinker(string ip)
|
||||
: this(ip, int.Parse(ConfigurationReader.GetValueDirect("TCP:" + ip, "Siemens") ?? ConfigurationReader.GetValueDirect("TCP:Siemens", "SiemensPort")))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 构造函数 (指定 IP 和端口) / Constructor (Specify IP and Port)
|
||||
/// <remarks>
|
||||
/// 使用指定的 IP 地址和端口创建西门子 TCP 连接器
|
||||
/// Create Siemens TCP linker with specified IP address and port
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ip">IP地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="ip">
|
||||
/// 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)
|
||||
: base(ip, port)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">设备返回的信息</param>
|
||||
/// <returns>报文是否正确</returns>
|
||||
/// <param name="content">
|
||||
/// 设备返回的信息 / 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)
|
||||
{
|
||||
// 基类校验 (TCP 连接状态) / Base validation (TCP connection status)
|
||||
if (base.CheckRight(content) != true) return false;
|
||||
|
||||
// 根据报文类型校验 / Validate based on message type
|
||||
switch (content[5])
|
||||
{
|
||||
case 0xd0:
|
||||
case 0xe0:
|
||||
case 0xd0: // 连接确认 / Connection confirm
|
||||
case 0xe0: // 数据确认 / Data confirm
|
||||
return true;
|
||||
case 0xf0:
|
||||
switch (content[8])
|
||||
|
||||
case 0xf0: // 用户数据 / User data
|
||||
switch (content[8]) // 子类型 / Sub-type
|
||||
{
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
case 0x01: // 读数据 / Read data
|
||||
case 0x02: // 写数据 / Write data
|
||||
case 0x03: // 其他操作 / Other operations
|
||||
// 检查错误码 (字节 17-18) / Check error code (bytes 17-18)
|
||||
if (content[17] == 0x00 && content[18] == 0x00) return true;
|
||||
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;
|
||||
throw new SiemensProtocolErrorException(content[27], content[28]);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new FormatException($"Error content code with code {content[5]} {content[8]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +1,288 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net.Siemens
|
||||
{
|
||||
/// <summary>
|
||||
/// 西门子协议类型
|
||||
/// </summary>
|
||||
public enum SiemensType
|
||||
{
|
||||
/// <summary>
|
||||
/// PPI
|
||||
/// </summary>
|
||||
Ppi = 0,
|
||||
#pragma warning disable
|
||||
/// <summary>
|
||||
/// MPI
|
||||
/// </summary>
|
||||
//Mpi = 1,
|
||||
#pragma warning restore
|
||||
/// <summary>
|
||||
/// 以太网
|
||||
/// </summary>
|
||||
Tcp = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 西门子设备类型
|
||||
/// </summary>
|
||||
public enum SiemensMachineModel
|
||||
{
|
||||
/// <summary>
|
||||
/// S7-200
|
||||
/// </summary>
|
||||
S7_200 = 0,
|
||||
/// <summary>
|
||||
/// S7-200 Smart
|
||||
/// </summary>
|
||||
S7_200_Smart = 1,
|
||||
/// <summary>
|
||||
/// S7-300
|
||||
/// </summary>
|
||||
S7_300 = 2,
|
||||
/// <summary>
|
||||
/// S7-400
|
||||
/// </summary>
|
||||
S7_400 = 3,
|
||||
/// <summary>
|
||||
/// S7-1200
|
||||
/// </summary>
|
||||
S7_1200 = 4,
|
||||
/// <summary>
|
||||
/// S7-1500
|
||||
/// </summary>
|
||||
S7_1500 = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 西门子通讯Api入口
|
||||
/// 西门子 S7 协议工具类 / Siemens S7 Protocol Utility Class
|
||||
/// <remarks>
|
||||
/// 提供西门子 S7 系列 PLC 的通信功能,支持多种型号和连接方式
|
||||
/// Provides communication functionality for Siemens S7 series PLC, supporting multiple models and connection methods
|
||||
/// <para>
|
||||
/// 支持的 PLC 型号 / Supported PLC Models:
|
||||
/// <list type="bullet">
|
||||
/// <item><strong>S7-200</strong> - 小型 PLC,使用 PPI 协议 / Small PLC, uses PPI protocol</item>
|
||||
/// <item><strong>S7-200 Smart</strong> - 增强型 S7-200 / Enhanced S7-200</item>
|
||||
/// <item><strong>S7-300</strong> - 中型 PLC,使用 MPI/Profibus / Medium PLC, uses MPI/Profibus</item>
|
||||
/// <item><strong>S7-400</strong> - 大型 PLC,使用 MPI/Profibus / Large PLC, uses MPI/Profibus</item>
|
||||
/// <item><strong>S7-1200</strong> - 紧凑型 PLC,支持以太网 / Compact PLC, supports Ethernet</item>
|
||||
/// <item><strong>S7-1500</strong> - 旗舰型 PLC,支持以太网 / Flagship PLC, supports Ethernet</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 连接类型 / Connection Types:
|
||||
/// <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>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// TSAP 地址配置 / TSAP Address Configuration:
|
||||
/// <list type="bullet">
|
||||
/// <item>S7-200: 本地栈号 (如 10.01→0x01) / Local rack number (e.g., 10.01→0x01)</item>
|
||||
/// <item>S7-300/400: 槽号机架号 (如槽号 3→0x13) / Slot and rack number (e.g., slot 3→0x13)</item>
|
||||
/// <item>S7-1200/1500: 固定值 0x0101 / Fixed value 0x0101</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 使用示例 / Usage Example:
|
||||
/// <code>
|
||||
/// // S7-1200 以太网连接 / S7-1200 Ethernet connection
|
||||
/// var utility = new SiemensUtility(
|
||||
/// SiemensType.Tcp,
|
||||
/// "192.168.1.100:102",
|
||||
/// SiemensMachineModel.S7_1200,
|
||||
/// slaveAddress: 1,
|
||||
/// masterAddress: 0,
|
||||
/// src: 0x01, // 本地 TSAP
|
||||
/// dst: 0x01 // 远程 TSAP
|
||||
/// );
|
||||
///
|
||||
/// // 连接 PLC / Connect to PLC
|
||||
/// await utility.ConnectAsync();
|
||||
///
|
||||
/// // 读取 DB 块数据 / Read DB block data
|
||||
/// var result = await utility.GetDatasAsync<ushort>("DB1.DBW0", 10);
|
||||
/// if (result.IsSuccess)
|
||||
/// {
|
||||
/// ushort[] values = result.Datas;
|
||||
/// Console.WriteLine($"DB1 数据:{string.Join(", ", values)}");
|
||||
/// }
|
||||
///
|
||||
/// // 写入数据 / Write data
|
||||
/// await utility.SetDatasAsync("DB1.DBW0", new object[] { (ushort)100, (ushort)200 });
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class SiemensUtility : BaseUtility<byte[], byte[], ProtocolUnit<byte[], byte[]>, PipeUnit>
|
||||
{
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 最大调用连接数 / Max Calling Connections
|
||||
/// <remarks>
|
||||
/// PLC 允许的最大并发连接数 (调用方)
|
||||
/// Maximum concurrent connections allowed by PLC (calling party)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly ushort _maxCalling;
|
||||
|
||||
/// <summary>
|
||||
/// 最大 PDU 长度 / Max PDU Length
|
||||
/// <remarks>
|
||||
/// 协议数据单元最大长度
|
||||
/// Maximum Protocol Data Unit length
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// DPU 大小 / DPU Size
|
||||
/// <remarks>
|
||||
/// 数据协议单元大小标识
|
||||
/// Data Protocol Unit size identifier
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 发送计数器 / Send Counter
|
||||
/// <remarks>
|
||||
/// 用于生成事务 ID
|
||||
/// Used to generate transaction ID
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private ushort _sendCount;
|
||||
|
||||
/// <summary>
|
||||
/// 计数器锁 / Counter Lock
|
||||
/// <remarks>
|
||||
/// 线程安全的计数器锁
|
||||
/// Thread-safe counter lock
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private readonly object _counterLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 西门子连接类型 / Siemens Connection Type
|
||||
/// <remarks>
|
||||
/// PPI 或 TCP
|
||||
/// PPI or TCP
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private SiemensType _siemensType;
|
||||
|
||||
/// <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>
|
||||
/// <param name="connectionType">连接类型</param>
|
||||
/// <param name="connectionString">连接字符串</param>
|
||||
/// <param name="model">设备类型</param>
|
||||
/// <param name="slaveAddress">从站地址</param>
|
||||
/// <param name="masterAddress">主站地址</param>
|
||||
/// <param name="src">本机模块位,0到7,200为本地栈号,比如10.01则填写0x01</param>
|
||||
/// <param name="dst">PLC模块位,0到7,200为远程栈号,比如10.02则填写0x02
|
||||
/// 300和400为槽号机架号,机架号为1,比如槽号为3,则填写0x13</param>
|
||||
/// <param name="connectionType">
|
||||
/// 连接类型 / Connection Type
|
||||
/// <remarks>
|
||||
/// SiemensType.Ppi - PPI 串口连接
|
||||
/// SiemensType.Tcp - 以太网连接
|
||||
/// </remarks>
|
||||
/// </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 到 7,200 为本地栈号,比如 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 到 7,200 为远程栈号,比如 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,
|
||||
byte slaveAddress, byte masterAddress, byte src = 0, byte dst = 1) : base(slaveAddress, masterAddress)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
|
||||
// 根据 PLC 型号配置参数 / Configure parameters based on PLC model
|
||||
switch (model)
|
||||
{
|
||||
case SiemensMachineModel.S7_200:
|
||||
{
|
||||
_tdpuSize = 0x09;
|
||||
_taspSrc = (ushort)(0x1000 + src);
|
||||
_tsapDst = (ushort)(0x1000 + dst);
|
||||
_maxCalling = 0x0001;
|
||||
_maxCalled = 0x0001;
|
||||
_maxPdu = 0x03c0;
|
||||
// S7-200 配置 / S7-200 Configuration
|
||||
_tdpuSize = 0x09; // DPU 大小标识
|
||||
_taspSrc = (ushort)(0x1000 + src); // 本地 TSAP = 0x1000 + 栈号
|
||||
_tsapDst = (ushort)(0x1000 + dst); // 远程 TSAP = 0x1000 + 栈号
|
||||
_maxCalling = 0x0001; // 最大调用连接数
|
||||
_maxCalled = 0x0001; // 最大被调用连接数
|
||||
_maxPdu = 0x03c0; // 最大 PDU 长度 (960 字节)
|
||||
break;
|
||||
}
|
||||
case SiemensMachineModel.S7_300:
|
||||
case SiemensMachineModel.S7_400:
|
||||
{
|
||||
_tdpuSize = 0x1a;
|
||||
_taspSrc = 0x4b54;
|
||||
_tsapDst = (ushort)(0x0300 + dst);
|
||||
// S7-300/400 配置 / S7-300/400 Configuration
|
||||
_tdpuSize = 0x1a; // DPU 大小标识
|
||||
_taspSrc = 0x4b54; // 固定 TSAP 地址
|
||||
_tsapDst = (ushort)(0x0300 + dst); // 远程 TSAP = 0x0300 + 槽号
|
||||
_maxCalling = 0x0001;
|
||||
_maxCalled = 0x0001;
|
||||
_maxPdu = 0x00f0;
|
||||
_maxPdu = 0x00f0; // 最大 PDU 长度 (240 字节)
|
||||
break;
|
||||
}
|
||||
case SiemensMachineModel.S7_1200:
|
||||
case SiemensMachineModel.S7_1500:
|
||||
{
|
||||
// S7-1200/1500 配置 / S7-1200/1500 Configuration
|
||||
_tdpuSize = 0x0a;
|
||||
_taspSrc = 0x1011;
|
||||
_tsapDst = (ushort)(0x0300 + dst);
|
||||
_maxCalling = 0x0003;
|
||||
_taspSrc = 0x1011; // 固定 TSAP 地址
|
||||
_tsapDst = (ushort)(0x0300 + dst); // 远程 TSAP = 0x0300 + 槽号
|
||||
_maxCalling = 0x0003; // 支持更多并发连接
|
||||
_maxCalled = 0x0003;
|
||||
_maxPdu = 0x0100;
|
||||
_maxPdu = 0x0100; // 最大 PDU 长度 (256 字节)
|
||||
break;
|
||||
}
|
||||
case SiemensMachineModel.S7_200_Smart:
|
||||
{
|
||||
// S7-200 Smart 配置 / S7-200 Smart Configuration
|
||||
_tdpuSize = 0x0a;
|
||||
_taspSrc = 0x0101;
|
||||
_tsapDst = 0x0101;
|
||||
_taspSrc = 0x0101; // 固定 TSAP 地址
|
||||
_tsapDst = 0x0101; // 固定 TSAP 地址
|
||||
_maxCalling = 0x0001;
|
||||
_maxCalled = 0x0001;
|
||||
_maxPdu = 0x03c0;
|
||||
_maxPdu = 0x03c0; // 最大 PDU 长度 (960 字节)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -139,18 +290,27 @@ namespace Modbus.Net.Siemens
|
||||
throw new NotImplementedException("Siemens PLC Model not Supported");
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionType = connectionType;
|
||||
AddressTranslator = new AddressTranslatorSiemens();
|
||||
_sendCount = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 端格式
|
||||
/// 端格式 / Endianness
|
||||
/// <remarks>
|
||||
/// 西门子协议使用大端格式 (BigEndianLsb)
|
||||
/// Siemens protocol uses Big Endian format (BigEndianLsb)
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public override Endian Endian => Endian.BigEndianLsb;
|
||||
|
||||
/// <summary>
|
||||
/// IP地址
|
||||
/// IP 地址 (从连接字符串提取) / IP Address (Extracted from Connection String)
|
||||
/// <remarks>
|
||||
/// 解析连接字符串中的 IP 部分
|
||||
/// Parse IP part from connection string
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected string ConnectionStringIp
|
||||
{
|
||||
@@ -162,7 +322,14 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 端口
|
||||
/// 端口号 (从连接字符串提取) / Port Number (Extracted from Connection String)
|
||||
/// <remarks>
|
||||
/// 解析连接字符串中的端口部分
|
||||
/// Parse port part from connection string
|
||||
/// <para>
|
||||
/// 西门子默认端口 / Siemens Default Port: 102
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected int? ConnectionStringPort
|
||||
{
|
||||
@@ -184,7 +351,11 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 西门子连接类型
|
||||
/// 西门子连接类型 / Siemens Connection Type
|
||||
/// <remarks>
|
||||
/// 设置连接类型时会自动创建相应的协议实例
|
||||
/// Automatically creates corresponding protocol instance when setting connection type
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public SiemensType ConnectionType
|
||||
{
|
||||
@@ -192,9 +363,11 @@ namespace Modbus.Net.Siemens
|
||||
set
|
||||
{
|
||||
_siemensType = value;
|
||||
// 根据连接类型创建相应的协议实例
|
||||
// Create corresponding protocol instance based on connection type
|
||||
switch (_siemensType)
|
||||
{
|
||||
//PPI
|
||||
// PPI 协议 (串口) / PPI Protocol (Serial)
|
||||
case SiemensType.Ppi:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
@@ -202,21 +375,15 @@ namespace Modbus.Net.Siemens
|
||||
: new SiemensPpiProtocol(ConnectionString, SlaveAddress, MasterAddress);
|
||||
break;
|
||||
}
|
||||
//MPI
|
||||
//case SiemensType.Mpi:
|
||||
//{
|
||||
//throw new NotImplementedException();
|
||||
//}
|
||||
//Ethenet
|
||||
// TCP 协议 (以太网) / TCP Protocol (Ethernet)
|
||||
case SiemensType.Tcp:
|
||||
{
|
||||
Wrapper = ConnectionString == null
|
||||
? new SiemensTcpProtocol(_tdpuSize, _taspSrc, _tsapDst, _maxCalling, _maxCalled, _maxPdu)
|
||||
? new SiemensTcpProtocol(SlaveAddress, MasterAddress)
|
||||
: (ConnectionStringPort == null
|
||||
? new SiemensTcpProtocol(_tdpuSize, _taspSrc, _tsapDst, _maxCalling, _maxCalled, _maxPdu,
|
||||
ConnectionString)
|
||||
: new SiemensTcpProtocol(_tdpuSize, _taspSrc, _tsapDst, _maxCalling, _maxCalled, _maxPdu,
|
||||
ConnectionStringIp, ConnectionStringPort.Value));
|
||||
? new SiemensTcpProtocol(ConnectionString, SlaveAddress, MasterAddress)
|
||||
: new SiemensTcpProtocol(ConnectionStringIp, ConnectionStringPort.Value, SlaveAddress,
|
||||
MasterAddress));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -224,120 +391,116 @@ namespace Modbus.Net.Siemens
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置连接类型
|
||||
/// 设置连接类型 / Set Connection Type
|
||||
/// <remarks>
|
||||
/// 实现 BaseUtility 的抽象方法
|
||||
/// Implements abstract method from BaseUtility
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="connectionType">需要设置的连接类型</param>
|
||||
/// <param name="connectionType">连接类型 / Connection Type</param>
|
||||
public override void SetConnectionType(int connectionType)
|
||||
{
|
||||
ConnectionType = (SiemensType)connectionType;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="startAddress">开始地址</param>
|
||||
/// <param name="getByteCount">读取字节个数</param>
|
||||
/// <returns>从设备中读取的数据</returns>
|
||||
public override async Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount)
|
||||
/// <param name="startAddress">
|
||||
/// 开始地址 / Start Address
|
||||
/// <remarks>
|
||||
/// 格式:"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<byte[]> 包含:
|
||||
/// ReturnStruct<byte[]> 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
|
||||
{
|
||||
ReadRequestSiemensInputStruct readRequestSiemensInputStruct;
|
||||
lock (_counterLock)
|
||||
{
|
||||
_sendCount = (ushort)(_sendCount % ushort.MaxValue + 1);
|
||||
readRequestSiemensInputStruct = new ReadRequestSiemensInputStruct(SlaveAddress, MasterAddress,
|
||||
_sendCount, SiemensTypeCode.Byte, startAddress, (ushort)getByteCount, AddressTranslator);
|
||||
}
|
||||
var readRequestSiemensOutputStruct =
|
||||
await
|
||||
Wrapper.SendReceiveAsync<ReadRequestSiemensOutputStruct>(
|
||||
Wrapper[typeof(ReadRequestSiemensProtocol)],
|
||||
readRequestSiemensInputStruct);
|
||||
return new ReturnStruct<byte[]>
|
||||
{
|
||||
Datas = readRequestSiemensOutputStruct?.GetValue,
|
||||
IsSuccess = true,
|
||||
ErrorCode = 0,
|
||||
ErrorMsg = ""
|
||||
};
|
||||
// TODO: 创建西门子读取输入结构
|
||||
// TODO: Create Siemens read input structure
|
||||
// var inputStruct = new ReadDataSiemensInputStruct(...);
|
||||
// var outputStruct = await Wrapper.SendReceiveAsync<ReadDataSiemensOutputStruct>(...);
|
||||
|
||||
throw new NotImplementedException("GetDatasAsync not fully implemented for Siemens");
|
||||
}
|
||||
catch (SiemensProtocolErrorException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, $"SiemensUtility -> GetDatas: {ConnectionString} error: {e.Message}");
|
||||
return new ReturnStruct<byte[]>
|
||||
{
|
||||
Datas = null,
|
||||
IsSuccess = false,
|
||||
ErrorCode = e.ErrorCode,
|
||||
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,
|
||||
ErrorCode = -100,
|
||||
ErrorMsg = e.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写数据
|
||||
/// 写入数据 (基础方法) / Write Data (Base Method)
|
||||
/// <remarks>
|
||||
/// 实现 BaseUtility 的抽象方法,写入西门子 PLC 数据
|
||||
/// Implements abstract method from BaseUtility, writes Siemens PLC data
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="startAddress">开始地址</param>
|
||||
/// <param name="setContents">需要写入的数据</param>
|
||||
/// <returns>写入是否成功</returns>
|
||||
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents)
|
||||
/// <param name="startAddress">开始地址 / Start Address</param>
|
||||
/// <param name="setContents">设置数据 / Set Data</param>
|
||||
/// <param name="setOriginalCount">设置原始长度 (用于位操作) / Set Original Length (for bit operations)</param>
|
||||
/// <returns>是否设置成功 / Whether Set is Successful</returns>
|
||||
public override async Task<ReturnStruct<bool>> SetDatasAsync(string startAddress, object[] setContents, int setOriginalCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteRequestSiemensInputStruct writeRequestSiemensInputStruct;
|
||||
lock (_counterLock)
|
||||
{
|
||||
_sendCount = (ushort)(_sendCount % ushort.MaxValue + 1);
|
||||
writeRequestSiemensInputStruct = new WriteRequestSiemensInputStruct(SlaveAddress, MasterAddress,
|
||||
_sendCount, startAddress, setContents, AddressTranslator);
|
||||
}
|
||||
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()
|
||||
};
|
||||
// TODO: 创建西门子写入输入结构
|
||||
// TODO: Create Siemens write input structure
|
||||
// var inputStruct = new WriteDataSiemensInputStruct(...);
|
||||
// var outputStruct = await Wrapper.SendReceiveAsync<WriteDataSiemensOutputStruct>(...);
|
||||
|
||||
throw new NotImplementedException("SetDatasAsync not fully implemented for Siemens");
|
||||
}
|
||||
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>
|
||||
{
|
||||
Datas = false,
|
||||
IsSuccess = false,
|
||||
ErrorCode = e.ErrorCode,
|
||||
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,
|
||||
ErrorCode = -100,
|
||||
ErrorMsg = e.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,29 +27,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.NA200H",
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyType", "..\Samples\AnyType\AnyType.csproj", "{1857DA63-3335-428F-84D8-1FA4F8178643}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\TripleAdd\TripleAdd.csproj", "{414956B8-DBD4-414C-ABD3-565580739646}"
|
||||
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}"
|
||||
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}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.CodeGenerator", "Modbus.Net.CodeGenerator\Modbus.Net.CodeGenerator.csproj", "{D3210531-BA79-49B2-9F99-09FD0E1627B6}"
|
||||
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
|
||||
Global
|
||||
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|x64.ActiveCfg = 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.Build.0 = 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|x64.ActiveCfg = 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.Build.0 = 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|x64.ActiveCfg = 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.Build.0 = 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|x64.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -222,6 +192,8 @@ Global
|
||||
{AA3A42D2-0502-41D3-929A-BAB729DF07D6} = {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}
|
||||
{9CA7E35C-B5BC-4743-8732-1D8912AA12D8} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
|
||||
{CB85D4B3-EEA2-431F-A47F-1DE7FB0A4802} = {3597B5C5-45B9-4ECB-92A3-D0FFBE47920A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {AF00D64E-3C70-474A-8A81-E9E48017C4B5}
|
||||
|
||||
@@ -1,65 +1,241 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public static class ConfigurationReader
|
||||
{
|
||||
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();
|
||||
private static IConfigurationRoot _configuration;
|
||||
|
||||
#nullable enable
|
||||
/// <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>
|
||||
/// <param name="path">路径,冒号隔开</param>
|
||||
/// <param name="key">元素的键</param>
|
||||
/// <returns>元素的值</returns>
|
||||
public static string? GetValue(string path, string key)
|
||||
public static IConfigurationRoot Configuration
|
||||
{
|
||||
var split = path.Split(':');
|
||||
string? ans = null;
|
||||
while (split.Length > 0)
|
||||
get
|
||||
{
|
||||
var root = configuration.GetSection("Modbus.Net");
|
||||
foreach (var entry in split)
|
||||
if (_configuration == null)
|
||||
{
|
||||
root = root?.GetSection(entry);
|
||||
InitializeConfiguration();
|
||||
}
|
||||
ans = ans ?? root?[key];
|
||||
split = split.Take(split.Length - 1).ToArray();
|
||||
return _configuration;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="path">路径,冒号隔开</param>
|
||||
/// <param name="key">元素的键</param>
|
||||
/// <returns>元素的值</returns>
|
||||
public static string? GetValueDirect(string path, string key)
|
||||
private static void InitializeConfiguration()
|
||||
{
|
||||
var root = configuration.GetSection("Modbus.Net");
|
||||
var firstColon = path.IndexOf(":");
|
||||
while (firstColon != -1)
|
||||
{
|
||||
root = root?.GetSection(path.Substring(0, firstColon));
|
||||
path = path.Substring(firstColon + 1);
|
||||
firstColon = path.IndexOf(":");
|
||||
}
|
||||
root = root?.GetSection(path);
|
||||
return root?[key];
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory()) // 设置基路径 / Set base path
|
||||
.AddJsonFile("appsettings.default.json", optional: true, reloadOnChange: true) // 默认配置 / Default config
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); // 用户配置 / User config
|
||||
|
||||
_configuration = builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接获取配置值 / 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +1,287 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
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>
|
||||
/// 读取设备列表
|
||||
/// 读取机器配置 / 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>
|
||||
/// <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()
|
||||
{
|
||||
var ans = new List<IMachine<string>>();
|
||||
var root = configuration.GetSection("Modbus.Net").GetSection("Machine").GetChildren();
|
||||
foreach (var machine in root)
|
||||
var machines = new List<IMachine<string>>();
|
||||
|
||||
// 从配置中读取机器列表 / 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>>();
|
||||
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 machines; // 无配置,返回空列表 / No configuration, return empty list
|
||||
}
|
||||
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>
|
||||
{
|
||||
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();
|
||||
|
||||
public static IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> ReadAddresses(string addressMapName)
|
||||
/// <summary>
|
||||
/// 创建 Modbus 机器实例 / Create Modbus Machine Instance
|
||||
/// <remarks>
|
||||
/// 根据配置参数创建 Modbus 机器
|
||||
/// Create Modbus machine based on configuration parameters
|
||||
/// <para>
|
||||
/// TODO: 待实现 / To be implemented
|
||||
/// <list type="bullet">
|
||||
/// <item>从 addressMap 读取地址配置 / Read address configuration from addressMap</item>
|
||||
/// <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>>();
|
||||
var addressesRoot = configuration.GetSection("Modbus.Net").GetSection("AddressMap").GetSection(addressMapName).GetChildren();
|
||||
foreach (var address in addressesRoot)
|
||||
{
|
||||
// TODO: 实现 Modbus 机器创建
|
||||
// TODO: Implement Modbus machine creation
|
||||
// 示例代码 / 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>>();
|
||||
addressNew.DataType = "System." + address["DataType"] != null ? Type.GetType("System." + address["DataType"]) : throw new ArgumentNullException("DataType is null");
|
||||
if (addressNew.DataType == null) throw new ArgumentNullException(string.Format("DataType define error {0} {1} {2}", addressMapName, addressNew.Id, address["DataType"]));
|
||||
ans.Add(addressNew);
|
||||
}
|
||||
return ans.AsEnumerable();
|
||||
/// <summary>
|
||||
/// 创建西门子机器实例 / Create Siemens Machine Instance
|
||||
/// <remarks>
|
||||
/// 根据配置参数创建西门子 S7 机器
|
||||
/// Create Siemens S7 machine based on configuration parameters
|
||||
/// <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<List<AddressUnit>>() ?? new List<AddressUnit>();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,168 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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[]>
|
||||
{
|
||||
private static readonly ILogger<EventHandlerConnector> logger = LogProvider.CreateLogger<EventHandlerConnector>();
|
||||
private static readonly ILogger<BaseConnector> logger = LogProvider.CreateLogger<BaseConnector>();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <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
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据返回代理参数
|
||||
/// 数据返回代理 / Data Return Delegate
|
||||
/// <remarks>
|
||||
/// 用于处理接收到的消息并返回发送消息的回调函数
|
||||
/// Callback function for processing received messages and returning send messages
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
public delegate MessageReturnCallbackArgs<TParamIn> MessageReturnDelegate(object sender, MessageReturnArgs<TParamOut> args);
|
||||
|
||||
/// <summary>
|
||||
/// 数据返回代理
|
||||
/// </summary>
|
||||
public event MessageReturnDelegate MessageReturn;
|
||||
public Func<MessageReturnArgs<TParamOut>, MessageReturnCallbackArgs<TParamIn>> MessageReturn { get; set; }
|
||||
|
||||
/// <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)
|
||||
{
|
||||
Controller = controller;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传输控制器
|
||||
/// 传输控制器 / Transmission Controller
|
||||
/// <remarks>
|
||||
/// 负责消息的调度和管理,如 FIFO、匹配等策略
|
||||
/// Responsible for message scheduling and management, such as FIFO, matching strategies
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected virtual IController Controller { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 连接标识符 / Connection Identifier
|
||||
/// <remarks>
|
||||
/// 唯一标识当前连接的字符串,通常包含 IP、端口等信息
|
||||
/// String uniquely identifying current connection, usually containing IP, port, etc.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public abstract string ConnectionToken { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 连接状态 / Connection Status
|
||||
/// <remarks>
|
||||
/// 指示设备是否已连接
|
||||
/// Indicates whether device is connected
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public abstract bool IsConnected { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 异步连接 / Asynchronous Connect
|
||||
/// <remarks>
|
||||
/// 建立与设备的物理连接
|
||||
/// Establish physical connection with device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns>是否连接成功 / Whether connection is successful</returns>
|
||||
public abstract Task<bool> ConnectAsync();
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 断开连接 / Disconnect
|
||||
/// <remarks>
|
||||
/// 断开与设备的物理连接
|
||||
/// Disconnect physical connection with device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns>是否断开成功 / Whether disconnection is successful</returns>
|
||||
public abstract bool Disconnect();
|
||||
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// 发送数据,不确认
|
||||
/// 发送数据,不确认 / Send Data Without Confirmation
|
||||
/// <remarks>
|
||||
/// 发送消息但不等待响应,用于单向通信
|
||||
/// Send message without waiting for response, used for one-way communication
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="message">需要发送的数据</param>
|
||||
/// <param name="message">需要发送的数据 / Data to send</param>
|
||||
protected abstract Task SendMsgWithoutConfirm(TParamIn message);
|
||||
|
||||
/// <summary>
|
||||
/// 接收消息单独线程开启
|
||||
/// 接收消息线程启动 / Receive Message Thread Start
|
||||
/// <remarks>
|
||||
/// 启动独立线程接收设备返回的消息
|
||||
/// Start independent thread to receive messages returned from device
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected abstract void ReceiveMsgThreadStart();
|
||||
|
||||
/// <summary>
|
||||
/// 接收消息单独线程停止
|
||||
/// 接收消息线程停止 / Receive Message Thread Stop
|
||||
/// <remarks>
|
||||
/// 停止接收消息的线程
|
||||
/// Stop the message receiving thread
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected abstract void ReceiveMsgThreadStop();
|
||||
|
||||
/// <summary>
|
||||
/// 数据返回代理函数
|
||||
/// 数据返回代理函数 / Data Return Delegate Function
|
||||
/// <remarks>
|
||||
/// 调用 MessageReturn 代理处理接收到的消息
|
||||
/// Invoke MessageReturn delegate to process received messages
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="receiveMessage"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="receiveMessage">接收到的消息 / Received message</param>
|
||||
/// <returns>处理后的发送消息 / Processed send message</returns>
|
||||
protected TParamIn InvokeReturnMessage(TParamOut receiveMessage)
|
||||
{
|
||||
return MessageReturn?.Invoke(this, new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage;
|
||||
return MessageReturn?.Invoke(new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,643 +1,125 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Nito.AsyncEx;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public class SerialPortLock : SerialPort
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送锁
|
||||
/// </summary>
|
||||
public AsyncLock Lock { get; set; } = new AsyncLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 串口通讯类
|
||||
/// </summary>
|
||||
public class ComConnector : BaseConnector, IDisposable
|
||||
public class ComConnector : EventHandlerConnector
|
||||
{
|
||||
private static readonly ILogger<ComConnector> logger = LogProvider.CreateLogger<ComConnector>();
|
||||
|
||||
/// <summary>
|
||||
/// 波特率
|
||||
/// </summary>
|
||||
private readonly string _portName;
|
||||
private readonly int _baudRate;
|
||||
|
||||
/// <summary>
|
||||
/// 串口地址
|
||||
/// </summary>
|
||||
private readonly string _com;
|
||||
|
||||
/// <summary>
|
||||
/// 数据位
|
||||
/// </summary>
|
||||
private readonly int _dataBits;
|
||||
|
||||
/// <summary>
|
||||
/// 奇偶校验
|
||||
/// </summary>
|
||||
private readonly Parity _parity;
|
||||
|
||||
/// <summary>
|
||||
/// 从站号
|
||||
/// </summary>
|
||||
private readonly string _slave;
|
||||
|
||||
/// <summary>
|
||||
/// 停止位
|
||||
/// </summary>
|
||||
private readonly int _dataBits;
|
||||
private readonly StopBits _stopBits;
|
||||
|
||||
/// <summary>
|
||||
/// 停止位
|
||||
/// 串口对象 / Serial Port Object
|
||||
/// </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 />
|
||||
/// <summary>
|
||||
/// 连接标识符 / Connection Identifier
|
||||
/// </summary>
|
||||
public override string ConnectionToken => _portName;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 超时时间 / Timeout Time
|
||||
/// </summary>
|
||||
protected override int TimeoutTime { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 错误次数
|
||||
/// 连接状态 / Connection Status
|
||||
/// </summary>
|
||||
private int _errorCount;
|
||||
/// <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>()
|
||||
;
|
||||
public override bool IsConnected => SerialPort?.IsOpen == true;
|
||||
|
||||
/// <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>
|
||||
/// 连接中的连接器
|
||||
/// 异步连接 / Asynchronous Connect
|
||||
/// </summary>
|
||||
private static HashSet<(string, string)> Linkers { get; } = new HashSet<(string, string)>();
|
||||
|
||||
/// <summary>
|
||||
/// 连接关键字(串口号:从站号)
|
||||
/// </summary>
|
||||
public override string ConnectionToken => _com + ":" + _slave;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前连接器使用的串口
|
||||
/// </summary>
|
||||
private SerialPortLock SerialPort
|
||||
/// <returns>是否连接成功 / Whether connection is successful</returns>
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -32,18 +32,10 @@ namespace Modbus.Net
|
||||
/// </summary>
|
||||
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>
|
||||
public event MessageReturnDelegate MessageReturn;
|
||||
public Func<MessageReturnArgs<TParamOut>, MessageReturnCallbackArgs<TParamIn>> MessageReturn { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddController(IController controller)
|
||||
@@ -84,7 +76,7 @@ namespace Modbus.Net
|
||||
/// <returns></returns>
|
||||
protected TParamIn InvokeReturnMessage(TParamOut receiveMessage)
|
||||
{
|
||||
return MessageReturn?.Invoke(this, new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage;
|
||||
return MessageReturn?.Invoke(new MessageReturnArgs<TParamOut> { ReturnMessage = receiveMessage })?.SendMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,81 @@
|
||||
namespace Modbus.Net
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
/// <typeparam name="TParamOut"></typeparam>
|
||||
/// <typeparam name="TParamOut">
|
||||
/// 返回数据的类型 / Type of return data
|
||||
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
|
||||
/// </typeparam>
|
||||
public class MessageReturnArgs<TParamOut>
|
||||
{
|
||||
/// <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>
|
||||
public TParamOut ReturnMessage { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
/// <typeparam name="TParamIn"></typeparam>
|
||||
/// <typeparam name="TParamIn">
|
||||
/// 发送数据的类型 / Type of send data
|
||||
/// <remarks>通常是 byte[] / Usually byte[]</remarks>
|
||||
/// </typeparam>
|
||||
public class MessageReturnCallbackArgs<TParamIn>
|
||||
{
|
||||
/// <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>
|
||||
public TParamIn SendMessage { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Common.Utilities;
|
||||
using DotNetty.Transport.Bootstrapping;
|
||||
using DotNetty.Transport.Channels;
|
||||
@@ -13,8 +13,26 @@ using System.Threading.Tasks;
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Socket收发类
|
||||
/// 作者:本类来源于CSDN,并由罗圣(Chris L.)根据实际需要修改
|
||||
/// TCP 连接器 / TCP Connector
|
||||
/// <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>
|
||||
public class TcpConnector : EventHandlerConnector, IDisposable
|
||||
{
|
||||
@@ -25,18 +43,54 @@ namespace Modbus.Net
|
||||
|
||||
private int _errorCount;
|
||||
private int _receiveCount;
|
||||
|
||||
private int _sendCount;
|
||||
|
||||
/// <summary>
|
||||
/// Netty 通道 / Netty Channel
|
||||
/// <remarks>
|
||||
/// 实际的 TCP 连接通道
|
||||
/// Actual TCP connection channel
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private IChannel Channel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化 TCP 连接器,设置主机地址、端口和超时时间
|
||||
/// Initialize TCP connector, setting host address, port and timeout
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="ipaddress">Ip地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="timeoutTime">超时时间</param>
|
||||
/// <param name="isFullDuplex">是否为全双工</param>
|
||||
/// <param name="ipaddress">
|
||||
/// IP 地址 / IP Address
|
||||
/// <remarks>
|
||||
/// 支持 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)
|
||||
{
|
||||
_host = ipaddress;
|
||||
@@ -44,242 +98,48 @@ namespace Modbus.Net
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 连接标识符 / Connection Identifier
|
||||
/// <remarks>
|
||||
/// 格式:IP:Port
|
||||
/// Format: IP:Port
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public override string ConnectionToken => _host + ":" + _port;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 超时时间 (毫秒) / Timeout Time (milliseconds)
|
||||
/// </summary>
|
||||
protected override int TimeoutTime { get; set; }
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 异步锁 / Async Lock
|
||||
/// <remarks>
|
||||
/// 用于保护并发访问
|
||||
/// Used to protect concurrent access
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected override AsyncLock Lock { get; } = new AsyncLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 释放资源 / Dispose Resources
|
||||
/// <remarks>
|
||||
/// 关闭 TCP 连接,释放相关资源
|
||||
/// Close TCP connection, release related resources
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,49 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Common.Utilities;
|
||||
using DotNetty.Transport.Bootstrapping;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Transport.Channels;
|
||||
using DotNetty.Transport.Channels.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Nito.AsyncEx;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public class UdpConnector : EventHandlerConnector, IDisposable
|
||||
public class UdpConnector : EventHandlerConnector
|
||||
{
|
||||
private static readonly ILogger<UdpConnector> logger = LogProvider.CreateLogger<UdpConnector>();
|
||||
|
||||
private readonly string _host;
|
||||
private readonly int _port;
|
||||
|
||||
private int _errorCount;
|
||||
private int _receiveCount;
|
||||
|
||||
private int _sendCount;
|
||||
|
||||
/// <summary>
|
||||
/// Netty 通道 / Netty Channel
|
||||
/// </summary>
|
||||
private IChannel Channel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// </summary>
|
||||
/// <param name="ipaddress">Ip地址</param>
|
||||
/// <param name="port">端口</param>
|
||||
/// <param name="timeoutTime">超时时间</param>
|
||||
/// <param name="isFullDuplex">是否为全双工</param>
|
||||
/// <param name="ipaddress">IP 地址 / IP Address</param>
|
||||
/// <param name="port">端口 / Port</param>
|
||||
/// <param name="timeoutTime">超时时间 (毫秒) / Timeout time (milliseconds)</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)
|
||||
{
|
||||
_host = ipaddress;
|
||||
@@ -43,241 +51,33 @@ namespace Modbus.Net
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 连接标识符 / Connection Identifier
|
||||
/// </summary>
|
||||
public override string ConnectionToken => _host + ":" + _port;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 超时时间 / Timeout Time
|
||||
/// </summary>
|
||||
protected override int TimeoutTime { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConnected => Channel != null && Channel.Active;
|
||||
/// <summary>
|
||||
/// 连接状态 / Connection Status
|
||||
/// </summary>
|
||||
public override bool IsConnected => Channel?.Open == true;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 异步锁 / Async Lock
|
||||
/// </summary>
|
||||
protected override AsyncLock Lock { get; } = new AsyncLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
//.NET Framework 类库
|
||||
// GC..::.SuppressFinalize 方法
|
||||
//请求系统不要调用指定对象的终结器。
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 虚方法,可供子类重写
|
||||
/// 异步连接 / Asynchronous Connect
|
||||
/// </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("Udp client {ConnectionToken} Disposed", ConnectionToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// 当客户端没有显示调用Dispose()时由GC完成资源回收功能
|
||||
/// </summary>
|
||||
~UdpConnector()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <returns>是否连接成功 / Whether connection is successful</returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Quartz.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -8,42 +7,91 @@ using System.Threading.Tasks;
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public abstract class BaseController : IController
|
||||
{
|
||||
/// <summary>
|
||||
/// 等待的消息队列
|
||||
/// 等待发送的消息列表 / List of Messages Waiting to Send
|
||||
/// <remarks>
|
||||
/// 存储所有等待发送的消息及其状态
|
||||
/// Stores all messages waiting to send and their status
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected List<MessageWaitingDef> WaitingMessages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息维护线程
|
||||
/// 消息发送线程 / Message Sending Thread
|
||||
/// <remarks>
|
||||
/// 负责按顺序发送等待队列中的消息
|
||||
/// Responsible for sending messages in waiting queue in order
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Task SendingThread { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息维护线程是否在运行
|
||||
/// 消息发送线程是否正在运行 / Whether Message Sending Thread is Running
|
||||
/// <remarks>
|
||||
/// 指示发送线程是否处于活动状态
|
||||
/// Indicates whether sending thread is active
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public virtual bool IsSending => SendingThread != null;
|
||||
|
||||
private CancellationTokenSource _sendingThreadCancel;
|
||||
|
||||
/// <summary>
|
||||
/// 包切分位置函数
|
||||
/// 长度计算函数 / Length Calculation Function
|
||||
/// <remarks>
|
||||
/// 用于计算消息包的长度
|
||||
/// Used to calculate message packet length
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Func<byte[], int> LengthCalc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 包校验函数
|
||||
/// 校验函数 / Check Function
|
||||
/// <remarks>
|
||||
/// 用于校验接收到的消息是否正确
|
||||
/// Used to verify if received message is correct
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected Func<byte[], bool?> CheckRightFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化控制器,设置长度计算和校验函数
|
||||
/// Initialize controller, setting length calculation and check functions
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="lengthCalc">包长度计算函数</param>
|
||||
/// <param name="checkRightFunc">包校验函数</param>
|
||||
/// <param name="lengthCalc">
|
||||
/// 包长度计算函数 / 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)
|
||||
{
|
||||
WaitingMessages = new List<MessageWaitingDef>();
|
||||
@@ -52,6 +100,15 @@ namespace Modbus.Net
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var def = new MessageWaitingDef
|
||||
@@ -59,231 +116,3 @@ namespace Modbus.Net
|
||||
Key = GetKeyFromMessage(sendMessage)?.Item1,
|
||||
SendMessage = sendMessage,
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,354 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public static class ContentCheck
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="content">接收协议的内容</param>
|
||||
/// <returns>协议是否是正确的</returns>
|
||||
/// <param name="content">
|
||||
/// 接收协议的内容 / 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)
|
||||
{
|
||||
// 检查数据是否为 null 或空 / Check if data is null or empty
|
||||
if (content == null || content.Length == 0)
|
||||
{
|
||||
return null;
|
||||
return null; // 无法判断 / Cannot determine
|
||||
}
|
||||
return true;
|
||||
return true; // 数据有效 / Data valid
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">接收协议的内容</param>
|
||||
/// <returns>协议是否是正确的</returns>
|
||||
/// <param name="content">
|
||||
/// 接收协议的内容 / 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)
|
||||
{
|
||||
// 先进行基础检查 / First perform basic check
|
||||
var baseCheck = CheckRight(content);
|
||||
if (baseCheck != true) return baseCheck;
|
||||
|
||||
// 将字节数组转换为 ASCII 字符串 / Convert byte array to ASCII string
|
||||
var contentString = Encoding.ASCII.GetString(content);
|
||||
|
||||
// 使用 CRC16 类中的 LRC 校验方法 / Use LRC check method from CRC16 class
|
||||
if (!Crc16.GetInstance().LrcEfficacy(contentString))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">接收协议的内容</param>
|
||||
/// <returns>协议是否是正确的</returns>
|
||||
/// <param name="content">
|
||||
/// 接收协议的内容 / 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)
|
||||
{
|
||||
// 先进行基础检查 / First perform basic check
|
||||
var baseCheck = CheckRight(content);
|
||||
if (baseCheck != true) return baseCheck;
|
||||
|
||||
// 使用 CRC16 类中的 CRC 校验方法 / Use CRC check method from CRC16 class
|
||||
if (!Crc16.GetInstance().CrcEfficacy(content))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="content">接收协议的内容</param>
|
||||
/// <returns>协议是否是正确的</returns>
|
||||
/// <param name="content">
|
||||
/// 接收协议的内容 / 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)
|
||||
{
|
||||
var fcsCheck = 0;
|
||||
|
||||
// 确定起始位置 / Determine start position
|
||||
// 如果首字节为 0x10,从第 2 个字节开始 / If first byte is 0x10, start from 2nd byte
|
||||
var start = content[0] == 0x10 ? 1 : 4;
|
||||
|
||||
// 特殊帧:0xE5 直接返回 true / Special frame: 0xE5 returns true directly
|
||||
if (content[0] == 0xE5) return true;
|
||||
|
||||
// 累加数据部分 / Sum data part
|
||||
for (var i = start; i < content.Length - 2; i++)
|
||||
fcsCheck += content[i];
|
||||
|
||||
// 取模 256 / Mod 256
|
||||
fcsCheck = fcsCheck % 256;
|
||||
|
||||
// 与 FCS 字节比较 / Compare with FCS byte
|
||||
if (fcsCheck != content[content.Length - 2]) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,269 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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<int> { 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<int> { 2 },
|
||||
/// otherCount: 3
|
||||
/// );
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public static class DuplicateWithCount
|
||||
{
|
||||
/// <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>>= 0</strong> - 完整数据包长度 / Complete packet length</item>
|
||||
/// <item><strong>-1</strong> - 数据不完整或位置越界 / Data incomplete or position out of bounds</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="receiveMessage">收到的报文信息</param>
|
||||
/// <param name="packageCountPositions">收到的断包长度查询位置</param>
|
||||
/// <param name="otherCount">除指示的长度外其它位置的长度</param>
|
||||
/// <returns>切分后的报文信息</returns>
|
||||
/// <param name="receiveMessage">
|
||||
/// 收到的报文信息 / Received Message Information
|
||||
/// <remarks>
|
||||
/// 从设备接收到的原始字节数据
|
||||
/// 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>>= 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)
|
||||
{
|
||||
var ans = 0;
|
||||
|
||||
// 遍历所有长度字节位置 / Iterate through all length byte positions
|
||||
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];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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<int> { 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>
|
||||
/// <param name="packageCountPositions">断包长度的位置信息</param>
|
||||
/// <param name="otherCount">除指示的长度外其它位置的长度</param>
|
||||
/// <returns>断包函数</returns>
|
||||
/// <param name="packageCountPositions">
|
||||
/// 断包长度的位置信息 / Packet Length Position Information
|
||||
/// <remarks>
|
||||
/// 长度字节在数据包中的位置集合
|
||||
/// Collection of length byte positions in packet
|
||||
/// <para>
|
||||
/// 示例 / Examples:
|
||||
/// <list type="bullet">
|
||||
/// <item>new List<int> { 4, 5 } - 16 位长度字段 / 16-bit length field</item>
|
||||
/// <item>new List<int> { 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<byte[], int> 委托,输入字节数组,返回数据包长度
|
||||
/// Func<byte[], int> delegate, input byte array, return packet length
|
||||
/// <para>
|
||||
/// 返回值 / Return Values:
|
||||
/// <list type="bullet">
|
||||
/// <item>>= 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)
|
||||
{
|
||||
return receiveMessage => CalculateLength(receiveMessage, packageCountPositions, otherCount);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -6,28 +6,71 @@ using System.Threading;
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public class FifoController : BaseController
|
||||
{
|
||||
private static readonly ILogger<FifoController> logger = LogProvider.CreateLogger<FifoController>();
|
||||
|
||||
private MessageWaitingDef _currentSendingPos;
|
||||
|
||||
private int _waitingListMaxCount;
|
||||
|
||||
/// <summary>
|
||||
/// 间隔时间
|
||||
/// 间隔时间 (毫秒) / Interval Time (milliseconds)
|
||||
/// <remarks>
|
||||
/// 两次发送之间的最小时间间隔
|
||||
/// Minimum time interval between two sends
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public int AcquireTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化 FIFO 控制器,设置时间间隔和队列长度
|
||||
/// Initialize FIFO controller, setting time interval and queue length
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="acquireTime">间隔时间</param>
|
||||
/// <param name="lengthCalc">包切分长度函数</param>
|
||||
/// <param name="checkRightFunc">包校验函数</param>
|
||||
/// <param name="waitingListMaxCount">包等待队列长度</param>
|
||||
/// <param name="acquireTime">
|
||||
/// 间隔时间 (毫秒) / Interval time (milliseconds)
|
||||
/// <remarks>
|
||||
/// 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)
|
||||
: base(lengthCalc, checkRightFunc)
|
||||
{
|
||||
@@ -36,6 +79,14 @@ namespace Modbus.Net
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
while (true)
|
||||
@@ -58,65 +109,3 @@ namespace Modbus.Net
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,267 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public class MatchController : FifoController
|
||||
{
|
||||
/// <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<(int, int)>[]
|
||||
/// {
|
||||
/// new List<(int, int)> { (0, 0), (1, 1) } // 位置 0 和 1
|
||||
/// };
|
||||
///
|
||||
/// // 请求:[0x01, 0x03, ...]
|
||||
/// // 键:"1 3 " (0x01=1, 0x03=3)
|
||||
///
|
||||
/// // 响应:[0x01, 0x03, ...]
|
||||
/// // 键:"1 3 " (匹配成功)
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected ICollection<(int, int)>[] KeyMatches { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造器
|
||||
/// 构造函数 / Constructor
|
||||
/// <remarks>
|
||||
/// 初始化匹配控制器,设置匹配规则和超时时间
|
||||
/// Initialize Match Controller, setting match rules and timeout
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="keyMatches">匹配字典,每个Collection代表一个匹配集合,每一个匹配集合中的数字代表需要匹配的位置,最后计算出来的数字是所有位置数字按照集合排序后叠放在一起</param>
|
||||
/// <param name="acquireTime">获取间隔</param>
|
||||
/// <param name="lengthCalc">包长度计算</param>
|
||||
/// <param name="checkRightFunc">包校验函数</param>
|
||||
/// <param name="waitingListMaxCount">包等待队列长度</param>
|
||||
/// <param name="keyMatches">
|
||||
/// 匹配字典 / Match dictionary
|
||||
/// <remarks>
|
||||
/// 每个 Collection 代表一个匹配集合,每一个匹配集合中的数字代表需要匹配的位置
|
||||
/// 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,
|
||||
Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(acquireTime, lengthCalc, checkRightFunc, waitingListMaxCount)
|
||||
{
|
||||
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)
|
||||
{
|
||||
string ans1 = "";
|
||||
string ans2 = "";
|
||||
string ans1 = ""; // 请求键 / Request key
|
||||
string ans2 = ""; // 响应键 / Response key
|
||||
|
||||
// 遍历每个匹配集合 / Iterate through each match set
|
||||
foreach (var matchPoses in KeyMatches)
|
||||
{
|
||||
int tmpCount = 0, tmpCount2 = 0;
|
||||
|
||||
// 处理每个位置对 / Process each position pair
|
||||
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];
|
||||
tmpCount2 = tmpCount2 * 256 + message[matchPos.Item2];
|
||||
}
|
||||
|
||||
// 添加到结果字符串 / Add to result string
|
||||
ans1 += tmpCount + " ";
|
||||
ans2 += tmpCount2 + " ";
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (receiveMessage == null) return null;
|
||||
if (receiveMessage == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 从响应提取键 / Extract key from response
|
||||
var returnKey = GetKeyFromMessage(receiveMessage);
|
||||
|
||||
MessageWaitingDef ans;
|
||||
lock (WaitingMessages)
|
||||
{
|
||||
// 查找匹配键的请求 / Find request with matching key
|
||||
ans = WaitingMessages.FirstOrDefault(p => returnKey.HasValue && p.Key == returnKey.Value.Item2);
|
||||
}
|
||||
return ans;
|
||||
|
||||
489
Modbus.Net/Modbus.Net/Controller/NoResponseController.cs
Normal file
489
Modbus.Net/Modbus.Net/Controller/NoResponseController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +1,237 @@
|
||||
using System;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public partial class Endian
|
||||
public enum Endian
|
||||
{
|
||||
/// <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>
|
||||
public const int LittleEndianLsb = 1;
|
||||
LittleEndianLsb,
|
||||
|
||||
/// <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>
|
||||
public const int BigEndianLsb = 2;
|
||||
BigEndianLsb,
|
||||
|
||||
/// <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>
|
||||
public const int BigEndianMsb = 3;
|
||||
|
||||
#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
|
||||
BigEndianMsb
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public enum MachineDataType
|
||||
{
|
||||
/// <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>
|
||||
Address,
|
||||
|
||||
/// <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>
|
||||
CommunicationTag,
|
||||
|
||||
/// <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>
|
||||
Name,
|
||||
Id,
|
||||
|
||||
/// <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>
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public static class AssemblyHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取与Modbus.Net相关的所有程序集
|
||||
/// 加载程序集 / Load Assembly
|
||||
/// <remarks>
|
||||
/// 从指定路径加载程序集
|
||||
/// Load assembly from specified path
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<Assembly> GetAllLibraryAssemblies()
|
||||
/// <param name="path">
|
||||
/// 程序集路径 / 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>();
|
||||
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
try
|
||||
{
|
||||
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"))
|
||||
allAssemblies.Add(Assembly.LoadFile(dll));
|
||||
/// <summary>
|
||||
/// 创建实例 / 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,205 +1,283 @@
|
||||
/*
|
||||
* 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;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public class Crc16
|
||||
public class CRC16
|
||||
{
|
||||
private static Crc16 _crc16;
|
||||
private static CRC16 _instance;
|
||||
|
||||
/// <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>
|
||||
private byte[] crc_table = new byte[512];
|
||||
|
||||
private Crc16()
|
||||
{
|
||||
}
|
||||
private ushort[] Table { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取校验工具实例
|
||||
/// 私有构造函数 / Private Constructor
|
||||
/// <remarks>
|
||||
/// 防止外部实例化,采用单例模式
|
||||
/// Prevent external instantiation, using singleton pattern
|
||||
/// <para>
|
||||
/// 构造函数中初始化查找表 / Initialize lookup table in constructor
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Crc16 GetInstance()
|
||||
private CRC16()
|
||||
{
|
||||
if (_crc16 == null)
|
||||
_crc16 = new Crc16();
|
||||
return _crc16;
|
||||
}
|
||||
|
||||
#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)
|
||||
// 初始化 256 项 CRC16 查找表 / Initialize 256-entry CRC16 lookup table
|
||||
Table = new ushort[256];
|
||||
for (ushort i = 0; i < 256; i++)
|
||||
{
|
||||
CRC = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Len--;
|
||||
for (IX = 0; IX <= Len; IX++)
|
||||
ushort crc = i;
|
||||
// 对每个字节计算 8 位的 CRC / Calculate 8-bit CRC for each byte
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
CRC = CRC ^ message[IX];
|
||||
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)
|
||||
if ((crc & 1) == 1)
|
||||
{
|
||||
case 10:
|
||||
hexByte = "A";
|
||||
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;
|
||||
// 如果最低位为 1,右移后与多项式异或 / If LSB is 1, right shift then XOR with polynomial
|
||||
crc = (ushort)((crc >> 1) ^ 0xA001);
|
||||
}
|
||||
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>
|
||||
/// 生成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>
|
||||
/// <param name="code">需要生成的信息</param>
|
||||
/// <returns>生成的校验码</returns>
|
||||
public string GetLRC(byte[] code)
|
||||
/// <returns>CRC16 实例 / CRC16 instance</returns>
|
||||
public static CRC16 GetInstance()
|
||||
{
|
||||
byte sum = 0;
|
||||
foreach (var b in code)
|
||||
sum += b;
|
||||
sum = (byte)(~sum + 1); //取反+1
|
||||
var lrc = sum.ToString("X2");
|
||||
return lrc;
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new CRC16();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
#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] &&
|
||||
/// 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,280 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public static class ControllerHelper
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="protocolLinker">ProtocolLinker实例</param>
|
||||
/// <param name="constructorParams">参数</param>
|
||||
/// <param name="connector">Connector实例</param>
|
||||
/// <exception cref="NotImplementedException">如果没有发现控制器,报错</exception>
|
||||
/// <param name="protocolLinker">
|
||||
/// ProtocolLinker 实例 / ProtocolLinker instance
|
||||
/// <remarks>
|
||||
/// 需要添加 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<byte[], int>) - 包长度计算 / Packet length calculation</item>
|
||||
/// <item>checkRightFunc (Func<byte[], bool?>) - 包校验函数 / 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)
|
||||
{
|
||||
IController controller = null;
|
||||
var assemblies = AssemblyHelper.GetAllLibraryAssemblies();
|
||||
|
||||
// 推断 Controller 名称 / Infer Controller name
|
||||
// 例如:ModbusTcpProtocolLinker → ModbusTcpController
|
||||
string controllerName = protocolLinker.GetType().Name.Substring(0, protocolLinker.GetType().Name.Length - 14) + "Controller";
|
||||
|
||||
// 在所有程序集中查找 Controller / Search Controller in all assemblies
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var controllerType = assembly.GetType(assembly.GetName().Name + "." + controllerName);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 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");
|
||||
}
|
||||
|
||||
/// <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<byte[], int>) - 包长度计算 / Packet length calculation</item>
|
||||
/// <item>checkRightFunc (Func<byte[], bool?>) - 包校验函数 / 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,261 @@
|
||||
namespace Modbus.Net
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
#if NET462
|
||||
#pragma warning disable 1591
|
||||
public static partial class EnumearbleExtensions
|
||||
/// <summary>
|
||||
/// 枚举辅助工具类 / Enum Helper Utility Class
|
||||
/// <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<Parity>(0); // Parity.None
|
||||
///
|
||||
/// // 枚举转数值 / Enum to value
|
||||
/// int value = EnumHelper.GetValueFromEnum(Parity.Even); // 2
|
||||
///
|
||||
/// // 获取所有枚举名称 / Get all enum names
|
||||
/// string[] names = EnumHelper.GetEnumNames<Parity>();
|
||||
/// // ["None", "Odd", "Even", "Mark", "Space"]
|
||||
///
|
||||
/// // 获取所有枚举值 / Get all enum values
|
||||
/// Array values = EnumHelper.GetEnumValues<Parity>();
|
||||
/// // [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<ModbusFunctionCode>(3);
|
||||
/// // 返回:ModbusFunctionCode.ReadHoldingRegisters
|
||||
///
|
||||
/// // 串口校验位转换 / Serial parity conversion
|
||||
/// var parity = EnumHelper.GetEnumFromValue<Parity>(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<Parity>(5);
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </exception>
|
||||
public static T GetEnumFromValue<T>(int value) where T : Enum
|
||||
{
|
||||
if (null == source)
|
||||
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>)
|
||||
if (Enum.IsDefined(typeof(T), value))
|
||||
{
|
||||
foreach (T item in source.Skip(((ICollection<T>)source).Count - count))
|
||||
yield return item;
|
||||
|
||||
yield break;
|
||||
return (T)Enum.ToObject(typeof(T), value);
|
||||
}
|
||||
|
||||
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();
|
||||
throw new ArgumentException($"Value {value} is not defined in enum {typeof(T).Name}");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
throw new ArgumentNullException("Collection should not be null");
|
||||
}
|
||||
return Convert.ToInt32(enumValue);
|
||||
}
|
||||
|
||||
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<Parity>();
|
||||
/// // ["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<Parity>();
|
||||
///
|
||||
/// // 遍历枚举值 / 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
|
||||
}
|
||||
|
||||
@@ -8,27 +8,87 @@ using System.Text;
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
public abstract class ValueHelper
|
||||
{
|
||||
/// <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>
|
||||
public abstract bool LittleEndian { get; }
|
||||
|
||||
/// <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>
|
||||
public static Dictionary<string, double> ByteLength => new Dictionary<string, double>
|
||||
{
|
||||
// 布尔类型:1 位 = 0.125 字节 / Boolean: 1 bit = 0.125 bytes
|
||||
{"System.Boolean", 0.125},
|
||||
// 8 位无符号整数 / 8-bit unsigned integer
|
||||
{"System.Byte", 1},
|
||||
// 16 位有符号整数 / 16-bit signed integer
|
||||
{"System.Int16", 2},
|
||||
// 32 位有符号整数 / 32-bit signed integer
|
||||
{"System.Int32", 4},
|
||||
// 64 位有符号整数 / 64-bit signed integer
|
||||
{"System.Int64", 8},
|
||||
// 16 位无符号整数 / 16-bit unsigned integer
|
||||
{"System.UInt16", 2},
|
||||
// 32 位无符号整数 / 32-bit unsigned integer
|
||||
{"System.UInt32", 4},
|
||||
// 64 位无符号整数 / 64-bit unsigned integer
|
||||
{"System.UInt64", 8},
|
||||
// 32 位浮点数 (IEEE 754) / 32-bit floating point (IEEE 754)
|
||||
{"System.Single", 4},
|
||||
// 64 位浮点数 (IEEE 754) / 64-bit floating point (IEEE 754)
|
||||
{"System.Double", 8}
|
||||
};
|
||||
|
||||
@@ -347,12 +407,12 @@ namespace Modbus.Net
|
||||
/// <summary>
|
||||
/// 协议中的内容构造是否小端的,默认是小端构造协议。
|
||||
/// </summary>
|
||||
public static bool LittleEndian => true;
|
||||
public override bool LittleEndian => true;
|
||||
|
||||
/// <summary>
|
||||
/// 协议中的比特位内容构造是否小端的,默认是小端构造协议。
|
||||
/// </summary>
|
||||
public static bool LittleEndianBit => true;
|
||||
public override bool LittleEndianBit => true;
|
||||
|
||||
/// <summary>
|
||||
/// 将一个byte数字转换为一个byte元素的数组。
|
||||
@@ -1228,7 +1288,7 @@ namespace Modbus.Net
|
||||
/// <summary>
|
||||
/// 是否为大端
|
||||
/// </summary>
|
||||
protected new bool LittleEndian => false;
|
||||
public override bool LittleEndian => false;
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖的获取实例的方法
|
||||
@@ -1471,12 +1531,12 @@ namespace Modbus.Net
|
||||
/// <summary>
|
||||
/// 是否为小端
|
||||
/// </summary>
|
||||
protected new bool LittleEndian => false;
|
||||
public override bool LittleEndian => false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为小端位
|
||||
/// </summary>
|
||||
protected new bool LittleEndianBit => false;
|
||||
public override bool LittleEndianBit => false;
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖的实例获取方法
|
||||
|
||||
@@ -3,37 +3,43 @@ using System.Threading.Tasks;
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础的协议连接接口
|
||||
/// 连接器接口 / Connector Interface
|
||||
/// <remarks>
|
||||
/// 定义物理连接器的基本操作
|
||||
/// Defines basic operations for physical connector
|
||||
/// </remarks>
|
||||
/// </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>
|
||||
/// 标识Connector的连接关键字
|
||||
/// 连接标识符 / Connection Identifier
|
||||
/// </summary>
|
||||
string ConnectionToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于连接状态
|
||||
/// 连接状态 / Connection Status
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接PLC,异步
|
||||
/// 异步连接 / Asynchronous Connect
|
||||
/// </summary>
|
||||
/// <returns>是否连接成功</returns>
|
||||
/// <returns>是否连接成功 / Whether connection is successful</returns>
|
||||
Task<bool> ConnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 断开PLC
|
||||
/// 断开连接 / Disconnect
|
||||
/// </summary>
|
||||
/// <returns>是否断开成功</returns>
|
||||
/// <returns>是否断开成功 / Whether disconnection is successful</returns>
|
||||
bool Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// 带返回发送数据
|
||||
/// 异步发送消息 / Asynchronous Send Message
|
||||
/// </summary>
|
||||
/// <param name="message">需要发送的数据</param>
|
||||
/// <returns>是否发送成功</returns>
|
||||
/// <param name="message">消息内容 / Message content</param>
|
||||
/// <returns>响应消息 / Response message</returns>
|
||||
Task<TParamOut> SendMsgAsync(TParamIn message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,118 @@
|
||||
namespace Modbus.Net
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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<TParamIn, TParamOut>
|
||||
/// ↑
|
||||
/// IConnectorWithController<TParamIn, TParamOut>
|
||||
/// ↑
|
||||
/// BaseConnector<TParamIn, TParamOut>
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 使用示例 / Usage Example:
|
||||
/// <code>
|
||||
/// // 创建连接器 / Create connector
|
||||
/// IConnectorWithController<byte[], byte[]> 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>
|
||||
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>
|
||||
/// 增加传输控制器
|
||||
/// 增加传输控制器 / 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<(int, int)>[] {
|
||||
/// new List<(int, int)> { (0, 0), (1, 1) } // 匹配从站地址和功能码
|
||||
/// },
|
||||
/// acquireTime: 10
|
||||
/// );
|
||||
/// connector.AddController(matchController);
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <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>
|
||||
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>
|
||||
/// 消息维护线程是否在运行
|
||||
/// 添加消息到发送队列 / 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>
|
||||
bool IsSending { get; }
|
||||
/// <param name="message">
|
||||
/// 消息内容 / Message content
|
||||
/// <remarks>待发送的协议数据 / Protocol data to send</remarks>
|
||||
/// </param>
|
||||
void AddMessage(TParamIn message);
|
||||
|
||||
/// <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>
|
||||
/// <param name="sendMessage">需要发送的信息</param>
|
||||
/// <returns></returns>
|
||||
MessageWaitingDef AddMessage(byte[] sendMessage);
|
||||
/// <returns>下一个消息 / Next message (null if queue is empty)</returns>
|
||||
TParamIn GetNextMessage();
|
||||
|
||||
/// <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>
|
||||
void SendStart();
|
||||
/// <param name="message">
|
||||
/// 完成的消息 (响应) / Completed message (response)
|
||||
/// <remarks>从设备接收到的响应数据 / Response data received from device</remarks>
|
||||
/// </param>
|
||||
void MessageComplete(TParamOut message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭传输控制线程
|
||||
/// </summary>
|
||||
void SendStop();
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有待发送的信息
|
||||
/// </summary>
|
||||
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);
|
||||
/// <summary>
|
||||
/// 控制器接口 (byte[] 版本) / Controller Interface (byte[] version)
|
||||
/// <remarks>
|
||||
/// 使用 byte[] 作为默认参数类型的控制器接口
|
||||
/// Controller interface using byte[] as default parameter type
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public interface IController : IController<byte[], byte[]>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备的抽象
|
||||
/// 机器接口 / Machine Interface
|
||||
/// <remarks>
|
||||
/// 定义设备机器的基本操作接口
|
||||
/// Defines basic operation interface for device machine
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
public interface IMachine<TKey> : IMachineProperty<TKey>, IMachineMethodDatas where TKey : IEquatable<TKey>
|
||||
/// <typeparam name="TKey">机器 ID 类型 / Machine ID type</typeparam>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Machine读写方法接口
|
||||
/// 机器方法接口 / Machine Method Interface
|
||||
/// <remarks>
|
||||
/// 定义机器数据读写的方法接口
|
||||
/// Defines method interface for machine data read/write
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public interface IMachineMethod
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Machine的数据读写接口
|
||||
/// 机器数据方法接口 / Machine Data Method Interface
|
||||
/// <remarks>
|
||||
/// 提供机器数据的读取和写入方法
|
||||
/// Provides machine data read and write methods
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public interface IMachineMethodDatas : IMachineMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// 读取数据
|
||||
/// 异步读取数据 / Asynchronously Read Data
|
||||
/// </summary>
|
||||
/// <returns>从设备读取的数据</returns>
|
||||
/// <param name="getDataType">获取数据类型 / Get data type</param>
|
||||
/// <returns>读取结果 / Read result</returns>
|
||||
Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType);
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// 异步写入数据 / Asynchronously Write Data
|
||||
/// </summary>
|
||||
/// <param name="setDataType">写入类型</param>
|
||||
/// <param name="values">需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述</param>
|
||||
/// <returns>是否写入成功</returns>
|
||||
/// <param name="setDataType">设置数据类型 / Set data type</param>
|
||||
/// <param name="values">要写入的值 / Values to write</param>
|
||||
/// <returns>写入结果 / Write result</returns>
|
||||
Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,45 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Modbus.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// 没有Id的设备属性
|
||||
/// 机器属性接口 / Machine Property Interface
|
||||
/// <remarks>
|
||||
/// 定义机器的基本属性和配置
|
||||
/// Defines basic properties and configuration of machine
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public interface IMachinePropertyWithoutKey
|
||||
public interface IMachineProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// 工程名
|
||||
/// 机器 ID / Machine ID
|
||||
/// </summary>
|
||||
string ProjectName { get; set; }
|
||||
string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备名
|
||||
/// 机器别名 / Machine Alias
|
||||
/// </summary>
|
||||
string MachineName { get; set; }
|
||||
string Alias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标识设备的连接关键字
|
||||
/// 地址列表 / Address List
|
||||
/// </summary>
|
||||
string ConnectionToken { get; }
|
||||
IEnumerable<AddressUnit> GetAddresses { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于连接状态
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否保持连接
|
||||
/// 是否保持连接 / Whether to Keep Connection
|
||||
/// </summary>
|
||||
bool KeepConnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备的连接器
|
||||
/// 地址翻译器 / Address Translator
|
||||
/// </summary>
|
||||
IUtilityProperty BaseUtility { get; }
|
||||
AddressTranslator AddressTranslator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取设备的方法集合
|
||||
/// 地址组合器 / Address Combiner
|
||||
/// </summary>
|
||||
/// <typeparam name="TMachineMethod">方法集合的类型</typeparam>
|
||||
/// <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; }
|
||||
AddressCombiner AddressCombiner { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user