Compare commits
249 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
71ebb36fe9 | ||
![]() |
78a0b86327 | ||
![]() |
2636c16a97 | ||
![]() |
fd77c0242d | ||
![]() |
e74819a900 | ||
![]() |
9b7f696c9b | ||
![]() |
0230d614e7 | ||
![]() |
252d99ad78 | ||
![]() |
1ffc200350 | ||
![]() |
807d89b2b2 | ||
![]() |
4013afa1f1 | ||
![]() |
a580927ceb | ||
![]() |
bf2cf52034 | ||
![]() |
81bb8b7c31 | ||
![]() |
a825007fb5 | ||
![]() |
988124d96a | ||
![]() |
f0de815296 | ||
![]() |
0e2d58c887 | ||
![]() |
b155382626 | ||
![]() |
f362d740af | ||
![]() |
4a85e31a4f | ||
![]() |
302c270ad5 | ||
![]() |
3c1517d0f3 | ||
![]() |
f9fb222044 | ||
![]() |
e8edc02ba3 | ||
![]() |
95a44e3053 | ||
![]() |
74a9fe9a87 | ||
![]() |
4d03f9ea1a | ||
![]() |
67c96ca991 | ||
![]() |
88fb793c68 | ||
![]() |
d6d02d8cc5 | ||
![]() |
c5a3f8e2e3 | ||
![]() |
27e8653a1a | ||
![]() |
863beda82c | ||
![]() |
bac84c3ecd | ||
![]() |
2fca2ad9f8 | ||
![]() |
dd75286fe0 | ||
![]() |
7f91792cf1 | ||
![]() |
0e0ccad311 | ||
![]() |
0691f72e67 | ||
![]() |
7e38a51720 | ||
![]() |
34ca8243a3 | ||
![]() |
112fea7632 | ||
![]() |
378763e4ee | ||
![]() |
517bd0394d | ||
![]() |
70adb97fb5 | ||
![]() |
623d44cabe | ||
![]() |
0d479ca00b | ||
![]() |
8bc49ef437 | ||
![]() |
f83fcec786 | ||
![]() |
93690ce40d | ||
![]() |
f82c5f2f27 | ||
![]() |
a83c1c3899 | ||
![]() |
91d6aed109 | ||
![]() |
db8f8fe51d | ||
![]() |
4596004b17 | ||
![]() |
d5540906cb | ||
![]() |
90796a979d | ||
![]() |
2190a87772 | ||
![]() |
c5953b83f8 | ||
![]() |
24bc60abf0 | ||
![]() |
31eee6b009 | ||
![]() |
c5da565a8f | ||
![]() |
947cd712e1 | ||
![]() |
edc208f96b | ||
![]() |
1fb0296ee7 | ||
![]() |
6488d3df87 | ||
![]() |
56189d78e0 | ||
![]() |
bff18127b8 | ||
![]() |
363206e0ba | ||
![]() |
fd3e378501 | ||
![]() |
4ba2fe4c9d | ||
![]() |
2c499626ad | ||
![]() |
2b581a03c3 | ||
![]() |
450c15210a | ||
![]() |
65fed8cc93 | ||
![]() |
4b64771ea2 | ||
![]() |
f39977a6ff | ||
![]() |
933b535caa | ||
![]() |
8abc5d2f20 | ||
![]() |
d8783cd994 | ||
![]() |
d5d087feb5 | ||
![]() |
6ba3399df7 | ||
![]() |
65124b3aa8 | ||
![]() |
98597f4726 | ||
![]() |
e7981f0d8e | ||
![]() |
cf654427c3 | ||
![]() |
ff2f628282 | ||
![]() |
ae818ca265 | ||
![]() |
0f2aed458e | ||
![]() |
d486c44ff6 | ||
![]() |
ca7b9980bf | ||
![]() |
3c71e6a8e3 | ||
![]() |
542442864c | ||
![]() |
5edb64fa85 | ||
![]() |
8dc1c898a3 | ||
![]() |
1ed35726b0 | ||
![]() |
27fae9ebaa | ||
![]() |
b103f25c94 | ||
![]() |
abff450274 | ||
![]() |
c260736a11 | ||
![]() |
166ac2307a | ||
![]() |
b21a4e1a4d | ||
![]() |
f7dc943fa3 | ||
![]() |
bfbd2693ec | ||
![]() |
819e71c993 | ||
![]() |
9fd0b489a2 | ||
![]() |
f5fe9f8dae | ||
![]() |
f9ffc18145 | ||
![]() |
08db5b983a | ||
![]() |
5b3b4c8c50 | ||
![]() |
73f914ffc4 | ||
![]() |
d6bdd73ed6 | ||
![]() |
7370ee7349 | ||
![]() |
4574596bac | ||
![]() |
4d16855e36 | ||
![]() |
13a0d4d282 | ||
![]() |
b9cd06b829 | ||
![]() |
5b460e8fa2 | ||
![]() |
41087edf17 | ||
![]() |
2afcc38e38 | ||
![]() |
e59ccce25f | ||
![]() |
d7425890e8 | ||
![]() |
a989a837fb | ||
![]() |
db1221da50 | ||
![]() |
cf794569ed | ||
![]() |
51e5bbab0d | ||
![]() |
2c197ed2b2 | ||
![]() |
d8fc6665b3 | ||
![]() |
c671a79822 | ||
![]() |
9d93ce4c41 | ||
![]() |
a6d99fe227 | ||
![]() |
923b8bca31 | ||
![]() |
e2c30d1c88 | ||
![]() |
b6d9f2a04e | ||
![]() |
57306ea664 | ||
![]() |
cd7f3fd02f | ||
![]() |
0482e077a8 | ||
![]() |
5f986a45ca | ||
![]() |
ca7b49c0d5 | ||
![]() |
52dd555e6c | ||
![]() |
579b1a59f9 | ||
![]() |
5299c5c4be | ||
![]() |
f7756bccef | ||
![]() |
a6b874d160 | ||
![]() |
3e5fb3ddcf | ||
![]() |
5e6bcb12d3 | ||
![]() |
14303f1429 | ||
![]() |
96711ba022 | ||
![]() |
cbfc0fdbdc | ||
![]() |
6e81886c0e | ||
![]() |
2d976bc132 | ||
![]() |
57f6a476af | ||
![]() |
8491ed296e | ||
![]() |
cd1288afdc | ||
![]() |
ec6c830cb0 | ||
![]() |
2f86ccc4bf | ||
![]() |
8ca445aec0 | ||
![]() |
1e1f27c8a5 | ||
![]() |
2b84bde367 | ||
![]() |
b52e58551d | ||
![]() |
9aceed00bf | ||
![]() |
58814f7f74 | ||
![]() |
6a70ef9f31 | ||
![]() |
82cc4ca500 | ||
![]() |
4567fa04ed | ||
![]() |
8b98b5d818 | ||
![]() |
176d0351af | ||
![]() |
d63dc3384b | ||
![]() |
1ccd704e30 | ||
![]() |
f5d23dbe79 | ||
![]() |
75bfe53ac3 | ||
![]() |
3308f916dd | ||
![]() |
e7140279ca | ||
![]() |
1034719f5e | ||
![]() |
2c00043a7f | ||
![]() |
65c695d9ce | ||
![]() |
57253fe46a | ||
![]() |
4e5c443440 | ||
![]() |
0b3b73d8ec | ||
![]() |
921eabc134 | ||
![]() |
0faa428751 | ||
![]() |
f71a2fdd63 | ||
![]() |
4eb9ed8aba | ||
![]() |
d7b549abb8 | ||
![]() |
95d723c578 | ||
![]() |
2fcd853e86 | ||
![]() |
07eef7c812 | ||
![]() |
b01e0757fa | ||
![]() |
32844a20c6 | ||
![]() |
5b6532c601 | ||
![]() |
2c5b4b4027 | ||
![]() |
72d7ecf195 | ||
![]() |
2cfa6b4306 | ||
![]() |
6f6ffde0ab | ||
![]() |
1694739a16 | ||
![]() |
95d1e8bfca | ||
![]() |
60dec08e3c | ||
![]() |
a99d71be93 | ||
![]() |
f1331b6a0c | ||
![]() |
10d66b642b | ||
![]() |
cd2310e4a8 | ||
![]() |
1b399cf6b0 | ||
![]() |
877445bc0a | ||
![]() |
9a5b345bde | ||
![]() |
fc9e8ea7b3 | ||
![]() |
32be6fcfc1 | ||
![]() |
49847236c2 | ||
![]() |
d8424443e6 | ||
![]() |
f3b571ec3f | ||
![]() |
99318bb5d7 | ||
![]() |
1aa154c9aa | ||
![]() |
c65d8a445b | ||
![]() |
80f4f85570 | ||
![]() |
5beee43a6b | ||
![]() |
8d6ae203a0 | ||
![]() |
4353479a5c | ||
![]() |
34d7687f9e | ||
![]() |
b1dc3cf4af | ||
![]() |
6a58b95933 | ||
![]() |
d3badfd02b | ||
![]() |
0098be057b | ||
![]() |
6f972aa515 | ||
![]() |
7407ba6313 | ||
![]() |
1c79de207b | ||
![]() |
257c79db92 | ||
![]() |
9d1934a308 | ||
![]() |
d70f959902 | ||
![]() |
e4d810222f | ||
![]() |
bc1af4ae07 | ||
![]() |
6e688ef43f | ||
![]() |
f0fe1b23dc | ||
![]() |
aaf2006401 | ||
![]() |
b821e26935 | ||
![]() |
7ae4287157 | ||
![]() |
c6fcc38a65 | ||
![]() |
ab2d5c8853 | ||
![]() |
5e557ff0bc | ||
![]() |
918ca449a1 | ||
![]() |
8e73368008 | ||
![]() |
f3c1faf672 | ||
![]() |
d6df04dd6a | ||
![]() |
b1b9e51ab6 | ||
![]() |
e49d4770ac | ||
![]() |
8fa1075511 | ||
![]() |
9a70169b94 | ||
![]() |
fefb928237 | ||
![]() |
ad7e700d0d | ||
![]() |
1699c69147 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -360,4 +360,7 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
|
||||
|
||||
/framework/*Pro*
|
||||
|
@@ -5,10 +5,9 @@
|
||||
|
||||
**NetCore** 跨平台边缘采集网关(工业设备采集)
|
||||
|
||||
**ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/profiles/KimDiego)
|
||||
|
||||
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln)
|
||||
**ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
|
||||
|
||||
**ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin**
|
||||
|
||||
|
||||
## 文档
|
||||
@@ -23,7 +22,9 @@
|
||||
|
||||
[ThingsGateway演示地址](http://120.24.62.140:5000/)
|
||||
|
||||
账户 : **superAdmin** 密码 : **111111**
|
||||
账户 : **superAdmin**
|
||||
|
||||
密码 : **111111**
|
||||
|
||||
## 赞助
|
||||
|
||||
|
@@ -1,13 +1,10 @@
|
||||
[*.cs]
|
||||
|
||||
# CA1822: 将成员标记为 static
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
# CA1816: Dispose 方法应调用 SuppressFinalize
|
||||
dotnet_diagnostic.CA1816.severity = none
|
||||
# CA1848: 使用 LoggerMessage 委托
|
||||
dotnet_diagnostic.CA1848.severity = none
|
||||
|
||||
# CA2254: 模板应为静态表达式
|
||||
dotnet_diagnostic.CA2254.severity = none
|
||||
dotnet_diagnostic.CA2254.severity = suggestion
|
||||
|
||||
[*.cs]
|
||||
#### 命名样式 ####
|
||||
@@ -56,30 +53,6 @@ dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
csharp_style_var_when_type_is_apparent = false:silent
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
|
||||
# CA2208: 正确实例化参数异常
|
||||
dotnet_diagnostic.CA2208.severity = none
|
||||
|
||||
# IDE0057: 使用范围运算符
|
||||
dotnet_diagnostic.IDE0057.severity = none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
@@ -128,13 +101,3 @@ dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_event = false:silent
|
||||
end_of_line = crlf
|
||||
|
||||
# IDE0060: 删除未使用的参数
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
|
63
framework/.gitattributes
vendored
Normal file
63
framework/.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
364
framework/.gitignore
vendored
Normal file
364
framework/.gitignore
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
## 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/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# 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
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.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
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_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_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[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
|
||||
|
||||
# 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/
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.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 ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.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
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# 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/
|
||||
|
||||
# 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
|
||||
|
15
framework/Demo/Directory.Build.props
Normal file
15
framework/Demo/Directory.Build.props
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.0.20</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Authors>Diego</Authors>
|
||||
<Product>ThingsGateway</Product>
|
||||
<Copyright>© 2023-present Diego</Copyright>
|
||||
<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl>
|
||||
<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
|
||||
<GenerateDocumentationFile>False</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
</Project>
|
@@ -12,4 +12,5 @@
|
||||
|
||||
global using System;
|
||||
|
||||
global using TouchSocket.Core;
|
||||
global using ThingsGateway.Components;
|
||||
|
@@ -0,0 +1,38 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using Photino.Blazor;
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
[STAThread]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
|
||||
|
||||
var appBuilder = PhotinoBlazorAppBuilder.CreateDefault(args);
|
||||
|
||||
appBuilder.RootComponents.Add<App>("#app");
|
||||
|
||||
appBuilder.Services.ThingsGatewayComponentsConfigureServices();
|
||||
var app = appBuilder.Build();
|
||||
app.MainWindow.SetTitle("ThingsGateway.Foundation.Demo");
|
||||
app.MainWindow.SetIconFile("wwwroot/favicon.ico");
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
|
||||
{
|
||||
};
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="favicon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="favicon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Photino.Blazor" Version="2.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Demo.Rcl\ThingsGateway.Foundation.Demo.Rcl.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
BIN
framework/Demo/ThingsGateway.Foundation.Demo.Photino/favicon.ico
Normal file
BIN
framework/Demo/ThingsGateway.Foundation.Demo.Photino/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
26
framework/Demo/ThingsGateway.Foundation.Demo.Rcl/App.razor
Normal file
26
framework/Demo/ThingsGateway.Foundation.Demo.Rcl/App.razor
Normal file
@@ -0,0 +1,26 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
@@ -0,0 +1,148 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using ThingsGateway.Components;
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
/// <summary>
|
||||
/// 调试UI
|
||||
/// </summary>
|
||||
public abstract class DriverDebugUIBase : ComponentBase, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志缓存
|
||||
/// </summary>
|
||||
public ConcurrentLinkedList<(LogLevel level, string message)> Messages = new();
|
||||
|
||||
private PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
/// <inheritdoc/>
|
||||
~DriverDebugUIBase()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 变量地址
|
||||
/// </summary>
|
||||
public virtual string Address { get; set; } = "40001";
|
||||
|
||||
/// <summary>
|
||||
/// 长度
|
||||
/// </summary>
|
||||
public virtual int Length { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 默认读写设备
|
||||
/// </summary>
|
||||
public virtual IReadWrite Plc { get; set; }
|
||||
/// <summary>
|
||||
/// 写入值
|
||||
/// </summary>
|
||||
public virtual string WriteValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
protected virtual DataTypeEnum DataTypeEnum { get; set; } = DataTypeEnum.Int16;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public InitTimezone InitTimezone { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_periodicTimer?.Dispose();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void LogOut(ThingsGateway.Foundation.Core.LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
Messages.Add(((LogLevel)logLevel,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - {message} {exception}"));
|
||||
if (Messages.Count > 2500)
|
||||
{
|
||||
Messages.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task ReadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.ReadAsync(Address, Length, DataTypeEnum);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Messages.Add((LogLevel.Information,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - 对应类型值:{Environment.NewLine}{data.Content.ToJsonString(true)} "));
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Add((LogLevel.Warning,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - {data.Message}"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Messages.Add((LogLevel.Error,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - 错误:{ex}"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task WriteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.WriteAsync(Address, WriteValue, Length, DataTypeEnum);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Messages.Add((LogLevel.Information,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - {data.Message}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Add((LogLevel.Warning,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - {data.Message}"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Messages.Add((LogLevel.Error,
|
||||
$"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - 写入前失败:{ex}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (await _periodicTimer.WaitForNextTickAsync())
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,205 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using Microsoft.JSInterop;
|
||||
@using ThingsGateway.Foundation.Core;
|
||||
@using Masa.Blazor;
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@inherits DriverDebugUIBase
|
||||
|
||||
<MCard Elevation="1" Rounded="false" Class=" pa-2" Style="width:100%">
|
||||
<MRow Class="my-1" Justify="JustifyTypes.Start" Align="AlignTypes.Start" NoGutters>
|
||||
|
||||
<MCol Md="5">
|
||||
<MTabs @bind-Value="tab" Class="ma-2">
|
||||
<MTab Value=1> 读写测试 </MTab>
|
||||
<MTab Value=2> 特殊功能 </MTab>
|
||||
<MTab Value=3> 代码示例 </MTab>
|
||||
</MTabs>
|
||||
|
||||
<MTabsItems Value="tab">
|
||||
<MTabItem Value="1">
|
||||
@if (tab == 1)
|
||||
{
|
||||
@if (ReadWriteContent == null)
|
||||
{
|
||||
|
||||
<div class="my-1 py-1">
|
||||
<MTooltip Bottom Context="tip">
|
||||
<ActivatorContent>
|
||||
<MTextarea Class="mx-1 my-1" Label="变量地址" @attributes="@tip.Attrs" Dense Outlined HideDetails="@("auto")" @bind-Value=@Address />
|
||||
</ActivatorContent>
|
||||
<ChildContent>
|
||||
<span style="white-space: pre-wrap;">@Plc?.GetAddressDescription()</span>
|
||||
</ChildContent>
|
||||
</MTooltip>
|
||||
<MRow Class="my-1" Justify="JustifyTypes.Start" Align="AlignTypes.Start" NoGutters>
|
||||
<MTextField Class="mx-1 my-1" Style="max-width:200px" Label="读取长度" Dense Outlined HideDetails="@("auto")" @bind-Value=@Length />
|
||||
<MSelect Class="mx-1 my-1" Style="max-width:200px" @bind-Value="DataTypeEnum" Outlined Label="数据类型"
|
||||
Items=@(typeof(DataTypeEnum).GetEnumListWithOutSugar())
|
||||
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
|
||||
ItemText=@((u) =>u.Description)
|
||||
ItemValue=@(u =>(DataTypeEnum)u.Value)
|
||||
HideDetails=@("auto") Height="30"
|
||||
Dense>
|
||||
</MSelect>
|
||||
</MRow>
|
||||
|
||||
<MButton Class="mx-1 my-1" Color="primary" OnClick="ReadAsync">
|
||||
读取
|
||||
</MButton>
|
||||
<MTextarea Class="mx-1 mt-3 my-1" Label="值" Dense Outlined HideDetails="@("auto")" @bind-Value=@WriteValue />
|
||||
<MButton Class="mx-1 my-1" Color="primary" OnClick="WriteAsync">
|
||||
写入
|
||||
</MButton>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ReadWriteContent
|
||||
}
|
||||
}
|
||||
</MTabItem>
|
||||
|
||||
<MTabItem Value="2">
|
||||
@if (tab == 2)
|
||||
{
|
||||
@if (ShowDefaultOtherContent)
|
||||
{
|
||||
<MSubheader>
|
||||
连读打包
|
||||
</MSubheader>
|
||||
<MContainer>
|
||||
|
||||
@foreach (var item in DeviceVariableRunTimes)
|
||||
{
|
||||
<MRow Dense Align="AlignTypes.Center">
|
||||
<MTextField Class="ma-1" Outlined Style="min-width:100px" Label=@(item.DescriptionWithOutSugar(x => x.VariableAddress)) Dense HideDetails="@("auto")" @bind-Value=@item.VariableAddress></MTextField>
|
||||
<MSelect Class="mx-1 my-1" Style="max-width:120px" @bind-Value="item.DataTypeEnum" Outlined Label=@(item.DescriptionWithOutSugar(x => x.DataTypeEnum))
|
||||
Items=@(typeof(DataTypeEnum).GetEnumListWithOutSugar())
|
||||
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
|
||||
ItemText=@((u) =>u.Description)
|
||||
ItemValue=@(u =>(DataTypeEnum)u.Value)
|
||||
HideDetails=@("auto") Height="30"
|
||||
Dense>
|
||||
</MSelect>
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:100px" Label=@(item.DescriptionWithOutSugar(x => x.IntervalTime)) Dense HideDetails="@("auto")" @bind-Value=@item.IntervalTime></MTextField>
|
||||
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:100px" Label=实时值 Readonly ClearIcon="" Dense HideDetails="@("auto")" Value=item.Value?.ToJsonString()></MTextField>
|
||||
|
||||
</MRow>
|
||||
}
|
||||
<MRow Dense>
|
||||
<MTextField Class="ma-1" Outlined Style="max-width:100px" Label="打包长度" Dense HideDetails="@("auto")" @bind-Value=@MaxPack></MTextField>
|
||||
<MButton Class="ma-1" Color="primary" OnClick="MulReadAsync">
|
||||
读取
|
||||
</MButton>
|
||||
</MRow>
|
||||
</MContainer>
|
||||
|
||||
}
|
||||
|
||||
@if (OtherContent != null)
|
||||
{
|
||||
<MSheet Style="height:100%;overflow-y:auto">
|
||||
@OtherContent
|
||||
</MSheet>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
</MTabItem>
|
||||
|
||||
|
||||
<MTabItem Value="3">
|
||||
@if (tab == 3)
|
||||
{
|
||||
@if (CodeContent != null)
|
||||
@CodeContent
|
||||
else
|
||||
{
|
||||
|
||||
<MRow Align="AlignTypes.Center">
|
||||
<MContainer>
|
||||
|
||||
<MItemGroup @bind-Value="_selected" Class="shrink mr-6" Mandatory>
|
||||
@{
|
||||
|
||||
int index = 0;
|
||||
|
||||
}
|
||||
<MRow>
|
||||
|
||||
@foreach (var item in Sections)
|
||||
{
|
||||
<MItem Value="@(index++)">
|
||||
<div>
|
||||
<MButton IsActive="@context.Active" Icon OnClick="@context.Toggle">
|
||||
<MIcon>mdi-record</MIcon>
|
||||
</MButton>
|
||||
</div>
|
||||
</MItem>
|
||||
}
|
||||
</MRow>
|
||||
</MItemGroup>
|
||||
</MContainer>
|
||||
|
||||
<MCol>
|
||||
<MWindow Value="_selected" Vertical Class="elevation-1 grey lighten-5 rounded-b" Style=@($"height:450px;overflow:auto")>
|
||||
@{
|
||||
int index = 0;
|
||||
}
|
||||
@foreach (var item in Sections)
|
||||
{
|
||||
<MWindowItem Value="@(index++)">
|
||||
<AppCode RoundedTop0 Code="@item.Code" Language="@item.Language" />
|
||||
</MWindowItem>
|
||||
}
|
||||
</MWindow>
|
||||
</MCol>
|
||||
</MRow>
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</MTabItem>
|
||||
</MTabsItems>
|
||||
|
||||
</MCol>
|
||||
|
||||
<MCol Md="7">
|
||||
<MCard Style="overflow-y:auto;width:100%" Elevation="0" Flat Class="ml-4">
|
||||
<ConsoleTxt Messages="Messages" Height=500></ConsoleTxt>
|
||||
</MCard>
|
||||
</MCol>
|
||||
|
||||
</MRow>
|
||||
|
||||
</MCard>
|
||||
|
||||
|
||||
@code {
|
||||
StringNumber tab;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,236 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using BlazorComponent;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class DriverDebugUIPage : DriverDebugUIBase
|
||||
{
|
||||
/// <summary>
|
||||
/// DeviceVariableRunTimes
|
||||
/// </summary>
|
||||
public List<DeviceVariableRunTime> DeviceVariableRunTimes;
|
||||
/// <summary>
|
||||
/// MaxPack
|
||||
/// </summary>
|
||||
public int MaxPack = 100;
|
||||
/// <summary>
|
||||
/// MulReadAsync
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task MulReadAsync()
|
||||
{
|
||||
var deviceVariableSourceReads = Plc.LoadSourceRead<DeviceVariableSourceRead, DeviceVariableRunTime>(DeviceVariableRunTimes, MaxPack);
|
||||
foreach (var item in deviceVariableSourceReads)
|
||||
{
|
||||
var result = await Plc.ReadAsync(item.VariableAddress, item.Length);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.DeviceVariableRunTimes.PraseStructContent(Plc, result.Content);
|
||||
Messages.Add((Microsoft.Extensions.Logging.LogLevel.Information, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + result.Content.ToHexString(' ')));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Messages.Add((Microsoft.Extensions.Logging.LogLevel.Warning, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + ex));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
Messages.Add((Microsoft.Extensions.Logging.LogLevel.Warning, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset) + " - " + result.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private StringNumber _selected = 0;
|
||||
/// <summary>
|
||||
/// Sections
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public List<(string Code, string Language)> Sections { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// ShowDefaultOtherContent
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowDefaultOtherContent { get; set; } = true;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
DeviceVariableRunTimes = new()
|
||||
{
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40001",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40011",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40031",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40101",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
};
|
||||
Sections.Add((
|
||||
"""
|
||||
/// <inheritdoc/>
|
||||
public class DeviceVariableSourceRead : IDeviceVariableSourceRead<DeviceVariableRunTime>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public TimerTick TimerTick { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public string VariableAddress { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public int Length { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public List<DeviceVariableRunTime> DeviceVariableRunTimes { get; set; } = new List<DeviceVariableRunTime>();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public class DeviceVariableRunTime : IDeviceVariableRunTime
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[Description("读取间隔")]
|
||||
public int IntervalTime { get; set; }
|
||||
/// <inheritdoc/>
|
||||
[Description("变量地址")]
|
||||
public string VariableAddress { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public int Index { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public IThingsGatewayBitConverter ThingsGatewayBitConverter { get; set; }
|
||||
/// <inheritdoc/>
|
||||
[Description("数据类型")]
|
||||
public DataTypeEnum DataTypeEnum { get; set; }
|
||||
/// <inheritdoc/>
|
||||
[Description("实时值")]
|
||||
public object Value { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public OperResult SetValue(object value)
|
||||
{
|
||||
Value = value;
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
}
|
||||
public List<DeviceVariableRunTime> DeviceVariableRunTimes;
|
||||
|
||||
private static async Task ModbusClientAsync(IReadWrite plc)
|
||||
{
|
||||
DeviceVariableRunTimes = new()
|
||||
{
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40001",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40011",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40031",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new DeviceVariableRunTime()
|
||||
{
|
||||
DataTypeEnum=DataTypeEnum.Int16,
|
||||
VariableAddress="40101",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
};
|
||||
|
||||
#region 连读
|
||||
var deviceVariableSourceReads = Plc.LoadSourceRead<DeviceVariableSourceRead, DeviceVariableRunTime>(DeviceVariableRunTimes, MaxPack);
|
||||
foreach (var item in deviceVariableSourceReads)
|
||||
{
|
||||
var result = await Plc.ReadAsync(item.VariableAddress, item.Length);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
item.DeviceVariableRunTimes.PraseStructContent(result.Content);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
""", "csharp"));
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment ReadWriteContent { get; set; }
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment OtherContent { get; set; }
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment CodeContent { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
~DriverDebugUIPage()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public override IReadWrite Plc { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
Plc?.SafeDispose();
|
||||
base.Dispose();
|
||||
}
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
base.OnAfterRender(firstRender);
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using Masa.Blazor
|
||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
|
||||
<div class="mb-4">通道配置</div>
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
<MTextField Style="max-width:100px" Class="ma-1" Outlined Label=@(serialProperty.DescriptionWithOutSugar(x => x.PortName)) Dense HideDetails="@("auto")" @bind-Value=@serialProperty.PortName />
|
||||
<MTextField Style="max-width:100px" Class="ma-1" Outlined Label=@(serialProperty.DescriptionWithOutSugar(x => x.BaudRate)) Dense HideDetails="@("auto")" @bind-Value=@serialProperty.BaudRate />
|
||||
<MTextField Style="max-width:100px" Class="ma-1" Outlined Label=@(serialProperty.DescriptionWithOutSugar(x => x.DataBits)) Dense HideDetails="@("auto")" @bind-Value=@serialProperty.DataBits />
|
||||
<MSelect Class="ma-1 " Style="max-width:200px" Outlined @bind-Value="serialProperty.Parity" Label="@(serialProperty.DescriptionWithOutSugar(x => x.Parity))"
|
||||
Items=@(typeof(Parity).GetEnumListWithOutSugar())
|
||||
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
|
||||
ItemText=@((u) =>u.Description)
|
||||
ItemValue=@(u =>(Parity)u.Value)
|
||||
HideDetails=@("auto") Height="30"
|
||||
Dense>
|
||||
</MSelect>
|
||||
<MSelect Class="ma-1 " Style="max-width:200px" Outlined @bind-Value="serialProperty.StopBits" Label="@(serialProperty.DescriptionWithOutSugar(x => x.StopBits))"
|
||||
Items=@(typeof(StopBits).GetEnumListWithOutSugar())
|
||||
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
|
||||
ItemText=@((u) =>u.Description)
|
||||
ItemValue=@(u =>(StopBits)u.Value)
|
||||
HideDetails=@("auto") Height="30"
|
||||
Dense>
|
||||
</MSelect>
|
||||
<MButton Class="ma-1" OnClick=@ConnectAsync Color="primary">
|
||||
连接
|
||||
</MButton>
|
||||
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
|
||||
断开
|
||||
</MButton>
|
||||
</MRow>
|
||||
</MCard>
|
@@ -0,0 +1,106 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class SerialSessionPage : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志输出
|
||||
/// </summary>
|
||||
public Action<LogLevel, object, string, Exception> LogAction;
|
||||
|
||||
private TouchSocketConfig config;
|
||||
|
||||
private readonly SerialProperty serialProperty = new();
|
||||
|
||||
private SerialSession SerialSession { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public SerialSession GetSerialSession()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetSerialProperty(serialProperty);
|
||||
//载入配置
|
||||
SerialSession.Setup(config);
|
||||
return SerialSession;
|
||||
}
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
SerialSession.Close();
|
||||
await GetSerialSession().ConnectAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogAction?.Invoke(LogLevel.Error, null, null, ex);
|
||||
}
|
||||
|
||||
}
|
||||
private void DisConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
SerialSession.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogAction?.Invoke(LogLevel.Error, null, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
SerialSession.Setup(config);
|
||||
}
|
||||
base.OnAfterRender(firstRender);
|
||||
}
|
||||
|
||||
private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
SerialSession.SafeDispose();
|
||||
}
|
||||
internal void StateHasChangedAsync()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
@@ -10,21 +10,19 @@
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Blazor
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using System.Collections.Concurrent;
|
||||
@using ThingsGateway.Foundation;
|
||||
@using ThingsGateway.Foundation.Serial;
|
||||
@using ThingsGateway.Foundation.Core;
|
||||
@using Masa.Blazor
|
||||
@using TouchSocket.Core;
|
||||
@using TouchSocket.Sockets;
|
||||
@implements IDisposable
|
||||
<MCard Class="pa-4" Flat Elevation="0" Rounded="false">
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start">
|
||||
<MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
|
||||
<MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
|
||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
|
||||
<div class="mb-4">通道配置</div>
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
|
||||
<MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
|
||||
<MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
|
||||
|
||||
<MButton Class="ma-1" OnClick=@ConnectAsync Color="primary">
|
||||
连接
|
||||
@@ -33,7 +31,4 @@
|
||||
断开
|
||||
</MButton>
|
||||
</MRow>
|
||||
|
||||
|
||||
|
||||
</MCard>
|
@@ -0,0 +1,116 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class TcpClientPage : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志输出
|
||||
/// </summary>
|
||||
public Action<LogLevel, object, string, Exception> LogAction;
|
||||
|
||||
private TouchSocketConfig config;
|
||||
/// <summary>
|
||||
/// IP
|
||||
/// </summary>
|
||||
private string IP = "127.0.0.1";
|
||||
/// <summary>
|
||||
/// Port
|
||||
/// </summary>
|
||||
public int Port { get; set; } = 502;
|
||||
|
||||
private TcpClient TcpClient { get; set; } = new();
|
||||
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
TcpClient.Close();
|
||||
await GetTcpClient().ConnectAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
LogAction?.Invoke(LogLevel.Error, null, null, ex);
|
||||
}
|
||||
|
||||
}
|
||||
private void DisConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
TcpClient.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
LogAction?.Invoke(LogLevel.Error, null, null, ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TcpClient GetTcpClient()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
|
||||
//载入配置
|
||||
TcpClient.Setup(config);
|
||||
return TcpClient;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
|
||||
TcpClient.Setup(config);
|
||||
}
|
||||
base.OnAfterRender(firstRender);
|
||||
}
|
||||
|
||||
private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
TcpClient.SafeDispose();
|
||||
}
|
||||
|
||||
internal void StateHasChangedAsync()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
@@ -10,21 +10,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Blazor
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using System.Collections.Concurrent;
|
||||
@using ThingsGateway.Foundation;
|
||||
@using ThingsGateway.Foundation.Serial;
|
||||
@using ThingsGateway.Foundation.Core;
|
||||
@using Masa.Blazor
|
||||
@using TouchSocket.Core;
|
||||
@using TouchSocket.Sockets;
|
||||
@implements IDisposable
|
||||
<MCard Class="pa-4" Flat Elevation="0" Rounded="false">
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start">
|
||||
<MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip />
|
||||
<MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port />
|
||||
|
||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
|
||||
<div class="mb-4">通道配置</div>
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
|
||||
<MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip />
|
||||
<MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port />
|
||||
|
||||
<MButton Class="ma-1" OnClick=@Connect Color="primary">
|
||||
连接
|
||||
@@ -32,8 +31,8 @@
|
||||
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
|
||||
断开
|
||||
</MButton>
|
||||
|
||||
|
||||
|
||||
</MRow>
|
||||
|
||||
|
||||
|
||||
</MCard>
|
||||
</MCard>
|
@@ -1,25 +1,22 @@
|
||||
#region copyright
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Blazor;
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class TcpServerPage
|
||||
public partial class TcpServerPage : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志输出
|
||||
/// 日志输出
|
||||
/// </summary>
|
||||
public Action<LogLevel, object, string, Exception> LogAction;
|
||||
|
||||
@@ -31,11 +28,6 @@ public partial class TcpServerPage
|
||||
|
||||
private TcpService TcpServer { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
TcpServer.SafeDispose();
|
||||
}
|
||||
private void Connect()
|
||||
{
|
||||
try
|
||||
@@ -62,39 +54,55 @@ public partial class TcpServerPage
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取对象
|
||||
/// 获取对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TcpService GetTcpServer()
|
||||
{
|
||||
config?.Dispose();
|
||||
config = new TouchSocketConfig();
|
||||
var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
config ??= new TouchSocketConfig();
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) });
|
||||
config.SetBufferLength(300);
|
||||
//载入配置
|
||||
//载入配置
|
||||
TcpServer.Setup(config);
|
||||
return TcpServer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
config?.Dispose();
|
||||
config = new TouchSocketConfig();
|
||||
var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) });
|
||||
config.SetBufferLength(300);
|
||||
TcpServer = new TcpService();
|
||||
TcpServer.Setup(config);
|
||||
base.OnInitialized();
|
||||
if (firstRender)
|
||||
{
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) });
|
||||
TcpServer.Setup(config);
|
||||
}
|
||||
base.OnAfterRender(firstRender);
|
||||
}
|
||||
|
||||
private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
TcpServer.SafeDispose();
|
||||
}
|
||||
internal void StateHasChangedAsync()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
@@ -10,21 +10,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Blazor
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@using BlazorComponent;
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using System.Collections.Concurrent;
|
||||
@using ThingsGateway.Foundation;
|
||||
@using ThingsGateway.Foundation.Serial;
|
||||
@using ThingsGateway.Foundation.Core;
|
||||
@using Masa.Blazor
|
||||
@using TouchSocket.Core;
|
||||
@using TouchSocket.Sockets;
|
||||
@implements IDisposable
|
||||
<MCard Class="pa-4" Flat Elevation="0" Rounded="false">
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start">
|
||||
<MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip />
|
||||
<MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port />
|
||||
|
||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
|
||||
<div class="mb-4">通道配置</div>
|
||||
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
|
||||
|
||||
<MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
|
||||
<MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
|
||||
|
||||
<MButton Class="ma-1" OnClick=Connect Color="primary">
|
||||
连接
|
||||
@@ -32,8 +31,7 @@
|
||||
<MButton Class="ma-1" OnClick=DisConnect Color="red">
|
||||
断开
|
||||
</MButton>
|
||||
|
||||
|
||||
</MRow>
|
||||
|
||||
|
||||
|
||||
</MCard>
|
@@ -0,0 +1,113 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class UdpSessionPage : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志输出
|
||||
/// </summary>
|
||||
public Action<LogLevel, object, string, Exception> LogAction;
|
||||
|
||||
private TouchSocketConfig config;
|
||||
/// <summary>
|
||||
/// IP
|
||||
/// </summary>
|
||||
public string IP = "127.0.0.1";
|
||||
/// <summary>
|
||||
/// Port
|
||||
/// </summary>
|
||||
public int Port = 502;
|
||||
|
||||
private UdpSession UdpSession { get; set; } = new();
|
||||
|
||||
private void Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
UdpSession.Stop();
|
||||
GetUdpSession().Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogAction?.Invoke(LogLevel.Error, null, null, ex);
|
||||
}
|
||||
|
||||
}
|
||||
private void DisConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
UdpSession.Stop();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
LogAction?.Invoke(LogLevel.Error, null, null, ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public UdpSession GetUdpSession()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
|
||||
config.SetBindIPHost(new IPHost(0));
|
||||
//载入配置
|
||||
UdpSession.Setup(config);
|
||||
return UdpSession;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
config ??= new TouchSocketConfig();
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
|
||||
LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
|
||||
config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
|
||||
config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
|
||||
config.SetBindIPHost(new IPHost(0));
|
||||
UdpSession.Setup(config);
|
||||
}
|
||||
base.OnAfterRender(firstRender);
|
||||
}
|
||||
private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
UdpSession.SafeDispose();
|
||||
}
|
||||
internal void StateHasChangedAsync()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class DeviceVariableRunTime : IDeviceVariableRunTime
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[Description("读取间隔")]
|
||||
public int IntervalTime { get; set; }
|
||||
/// <inheritdoc/>
|
||||
[Description("变量地址")]
|
||||
public string VariableAddress { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public int Index { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public IThingsGatewayBitConverter ThingsGatewayBitConverter { get; set; }
|
||||
/// <inheritdoc/>
|
||||
[Description("数据类型")]
|
||||
public DataTypeEnum DataTypeEnum { get; set; }
|
||||
/// <inheritdoc/>
|
||||
[Description("实时值")]
|
||||
public object Value { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public OperResult SetValue(object value, DateTime dateTime = default, bool isOnline = true)
|
||||
{
|
||||
Value = value;
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class DeviceVariableSourceRead : IDeviceVariableSourceRead<IDeviceVariableRunTime>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public TimerTick TimerTick { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public string VariableAddress { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public int Length { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public List<IDeviceVariableRunTime> DeviceVariableRunTimes { get; set; } = new List<IDeviceVariableRunTime>();
|
||||
}
|
@@ -11,9 +11,11 @@
|
||||
#endregion
|
||||
|
||||
global using System;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
global using TouchSocket.Core;
|
||||
global using TouchSocket.Sockets;
|
||||
global using ThingsGateway.Components;
|
||||
global using ThingsGateway.Foundation.Core;
|
||||
global using ThingsGateway.Foundation.Serial;
|
||||
global using ThingsGateway.Foundation.Sockets;
|
||||
|
@@ -0,0 +1,36 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@page "/"
|
||||
@layout BaseLayout
|
||||
@inject NavigationManager NavigationManager
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
@code {
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="firstRender"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
NavigationManager.NavigateTo("index");
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@page "/index"
|
||||
|
||||
<div class="ml-2">
|
||||
<div class="my-6 ">
|
||||
<MLabel Class="text-h3" Color="primary">ThingsGateway</MLabel>
|
||||
</div>
|
||||
<div>
|
||||
<strong class="text--lighten-1 text-h5 my-1">文档</strong>
|
||||
</div>
|
||||
<div class="my-2 ml-4">
|
||||
<PCopyableText>
|
||||
https://diego2098.gitee.io/thingsgateway-docs/
|
||||
</PCopyableText>
|
||||
</div>
|
||||
<div>
|
||||
<strong class="text--lighten-1 text-h5 my-1">协议</strong>
|
||||
</div>
|
||||
<div class="my-2 ml-4">
|
||||
<PCopyableText Text="https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE.zh">
|
||||
Apache-2.0开源协议
|
||||
</PCopyableText>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong class="text--lighten-1 text-h5 my-1">赞助</strong>
|
||||
</div>
|
||||
<div class="my-2 ml-4">
|
||||
<PCopyableText Text="https://diego2098.gitee.io/thingsgateway-docs/docs/donate">
|
||||
ThingsGateway赞助途径
|
||||
</PCopyableText>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong class="text--lighten-1 text-h5 my-1">社区</strong>
|
||||
</div>
|
||||
<div class="my-2 ml-4">
|
||||
<PCopyableText Text="605534569">
|
||||
QQ群:605534569
|
||||
</PCopyableText>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
@@ -0,0 +1,42 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<CascadingValue Value="IsMobile" Name="IsMobile">
|
||||
<MApp>
|
||||
<MErrorHandler>
|
||||
@Body
|
||||
</MErrorHandler>
|
||||
</MApp>
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
public bool IsMobile { get; set; }
|
||||
[Inject]
|
||||
public MasaBlazor MasaBlazor { get; set; }
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MasaBlazor.BreakpointChanged += BreakpointOnOnUpdate;
|
||||
}
|
||||
|
||||
private void BreakpointOnOnUpdate(object sender, BreakpointChangedEventArgs e)
|
||||
{
|
||||
IsMobile = MasaBlazor.Breakpoint.Mobile;
|
||||
if (e.MobileChanged)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,34 +10,18 @@
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@namespace ThingsGateway.Admin.Blazor
|
||||
@using Masa.Blazor.Presets
|
||||
@using ThingsGateway.Admin.Blazor.Core
|
||||
@namespace ThingsGateway.Foundation.Demo
|
||||
@using System.Text;
|
||||
@inherits LayoutComponentBase
|
||||
@layout BaseLayout
|
||||
@if (UserManager.UserId > 0)
|
||||
{
|
||||
<SysSignalR></SysSignalR>
|
||||
}
|
||||
|
||||
<PPageTabsProvider>
|
||||
|
||||
<CascadingValue Value="@this" IsFixed>
|
||||
<CascadingValue Value="@Changed" Name="Changed">
|
||||
|
||||
<MNavigationDrawer Color="barcolor" @bind-Value="_drawerOpen" App Width="200">
|
||||
@if (IsMobile)
|
||||
{
|
||||
<MSystemBar Color="barcolor" Height="@(BlazorResourceConst.PageTabsHeight)">
|
||||
<MButton Icon OnClick=@(()=> _drawerOpen = !_drawerOpen)>
|
||||
<MIcon>
|
||||
mdi-close-thick
|
||||
</MIcon>
|
||||
</MButton>
|
||||
<MSpacer />
|
||||
<AppbarButtons />
|
||||
</MSystemBar>
|
||||
}
|
||||
<Logo CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_COPYRIGHT_URL=@CONFIG_COPYRIGHT_URL CONFIG_TITLE=@CONFIG_TITLE HeightInt=@(IsMobile?BlazorResourceConst.AppBarHeight:BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight) />
|
||||
<Logo CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_TITLE=@CONFIG_TITLE HeightInt=@(IsMobile?BlazorResourceConst.AppBarHeight:BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight) />
|
||||
<AppList ClassString="overflow-y-auto" Routable
|
||||
StyleString=@($"height: calc(100vh - {BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight}px);")
|
||||
Items="Navs" />
|
||||
@@ -48,18 +32,16 @@
|
||||
<MButton Class="mr-0" Icon Small=IsMobile OnClick=@(() => _drawerOpen = !_drawerOpen)>
|
||||
<MIcon>mdi-menu</MIcon>
|
||||
</MButton>
|
||||
<AppBarItems CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_COPYRIGHT_URL=@CONFIG_COPYRIGHT_URL CONFIG_TITLE=@CONFIG_TITLE>
|
||||
</AppBarItems>
|
||||
|
||||
</MAppBar>
|
||||
|
||||
<MMain Style=@($"{(!(IsMobile||_drawerOpen!=true)? "padding-left:200px;":"")}")>
|
||||
<div class="full-width">
|
||||
<PageTabs @ref="_pageTabs" />
|
||||
<PageTabs @ref="_pageTabs" PageTabItems="pageTabItems" />
|
||||
</div>
|
||||
<MDivider Center></MDivider>
|
||||
<MCard Flat Class="overflow-y-auto overflow-x-hidden ma-auto pa-0 rounded-0" Style=@($"height: calc(100vh - {BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight+BlazorResourceConst.FooterHeight}px);")>
|
||||
<PPageContainer PageTabs="@_pageTabs?.PPageTabs" SelfPatterns="@selfPatterns">
|
||||
<PPageContainer PageTabs="@_pageTabs?.PPageTabs">
|
||||
@Body
|
||||
</PPageContainer>
|
||||
</MCard>
|
||||
@@ -71,5 +53,25 @@
|
||||
</CascadingValue>
|
||||
|
||||
</PPageTabsProvider>
|
||||
@code {
|
||||
bool Changed { get; set; }
|
||||
private bool? _drawerOpen = true;
|
||||
|
||||
private PageTabs _pageTabs;
|
||||
|
||||
/// <summary>
|
||||
/// IsMobile
|
||||
/// </summary>
|
||||
[CascadingParameter(Name = "IsMobile")]
|
||||
public bool IsMobile { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@code{
|
||||
private string CONFIG_COPYRIGHT = "Diego";
|
||||
private string CONFIG_COPYRIGHT_URL = "https://gitee.com/diego2098/ThingsGateway";
|
||||
private string CONFIG_TITLE = "ThingsGateway";
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,232 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
|
||||
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
|
||||
// Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
|
||||
// GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
|
||||
// GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
|
||||
// ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQȺ<51><C8BA>605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Demo;
|
||||
|
||||
public partial class MainLayout
|
||||
{
|
||||
|
||||
private List<NavItem> Navs { get; set; } = new();
|
||||
private List<PageTabItem> pageTabItems { get; set; } = new();
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var dataString =
|
||||
"""
|
||||
[
|
||||
{
|
||||
"Href": "/index",
|
||||
"Title": "<22><>ҳ"
|
||||
},
|
||||
{
|
||||
"Title": "Modbus",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/ModbusRtu",
|
||||
"Title": "ModbusRtu"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusTcp",
|
||||
"Title": "ModbusTcp"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusRtuOverTcp",
|
||||
"Title": "ModbusRtuOverTcp"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusRtuOverUdp",
|
||||
"Title": "ModbusRtuOverUdp"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusUdp",
|
||||
"Title": "ModbusUdp"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusTcpDtu",
|
||||
"Title": "ModbusTcpDtu"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusTcpServer",
|
||||
"Title": "ModbusTcpServer"
|
||||
},
|
||||
{
|
||||
"Href": "/ModbusSerialServer",
|
||||
"Title": "ModbusSerialServer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Siemens",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/S7_1500",
|
||||
"Title": "S7_1500"
|
||||
},
|
||||
{
|
||||
"Href": "/S7_1200",
|
||||
"Title": "S7_1200"
|
||||
},
|
||||
{
|
||||
"Href": "/S7_200",
|
||||
"Title": "S7_200"
|
||||
},
|
||||
{
|
||||
"Href": "/S7_200SMART",
|
||||
"Title": "S7_200SMART"
|
||||
},
|
||||
{
|
||||
"Href": "/S7_300",
|
||||
"Title": "S7_400"
|
||||
},
|
||||
{
|
||||
"Href": "/S7_400",
|
||||
"Title": "S7_400"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "DLT645",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/DLT645_2007",
|
||||
"Title": "DLT645_2007"
|
||||
},
|
||||
{
|
||||
"Href": "/DLT645_2007OverTcp",
|
||||
"Title": "DLT645_2007OverTcp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "OPCDA",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/OPCDAClient",
|
||||
"Title": "OPCDAClient"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "OPCUA",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/OPCUAClient",
|
||||
"Title": "OPCUAClient"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Mqtt",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/MqttClient",
|
||||
"Title": "MqttClient"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
""";
|
||||
Navs = dataString.FromJsonString<List<NavItem>>();
|
||||
|
||||
#if Pro
|
||||
var dataStringPro =
|
||||
"""
|
||||
[
|
||||
{
|
||||
"Title": "Melsec",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/QnA3E_Binary",
|
||||
"Title": "QnA3E_Binary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "ABCIP",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/ABCIPTCP",
|
||||
"Title": "ABCIPTCP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Omron",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/OmronFinsTcp",
|
||||
"Title": "OmronFinsTcp"
|
||||
},
|
||||
{
|
||||
"Href": "/OmronFinsUdp",
|
||||
"Title": "OmronFinsUdp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Secs",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/SecsTcp",
|
||||
"Title": "SecsTcp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "TS550",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/TS550",
|
||||
"Title": "TS550"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "Vigor",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/VigorSerial",
|
||||
"Title": "VigorSerial"
|
||||
},
|
||||
{
|
||||
"Href": "/VigorSerialOverTcp",
|
||||
"Title": "VigorSerialOverTcp"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Title": "GasCustom",
|
||||
"Children": [
|
||||
{
|
||||
"Href": "/GasCustomSerial",
|
||||
"Title": "GasCustomSerial"
|
||||
},
|
||||
{
|
||||
"Href": "/GasCustomSerialOverTcp",
|
||||
"Title": "GasCustomSerialOverTcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
|
||||
""";
|
||||
Navs.AddRange(dataStringPro.FromJsonString<List<NavItem>>());
|
||||
#endif
|
||||
pageTabItems = Navs.PasePageTabItem();
|
||||
base.OnInitialized();
|
||||
}
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup Condition="'$(SolutionName)'=='ThingsGateway - Pro'">
|
||||
<DefineConstants>Pro</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(SolutionName)'=='ThingsGateway - Pro'">
|
||||
|
||||
|
||||
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Melsec\Page\QnA3E_BinaryDebugPage.razor.cs" Link="Pages\Melsec\QnA3E_BinaryDebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Melsec\Page\QnA3E_BinaryDebugPage.razor" Link="Pages\Melsec\QnA3E_BinaryDebugPage.razor" />
|
||||
<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Melsec\ThingsGateway.Foundation.Adapter.Melsec.csproj" />
|
||||
|
||||
|
||||
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.AllenBradleyCip\Page\AllenBradleyCipTcpDebugPage.razor.cs" Link="Pages\ABCIP\AllenBradleyCipTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.AllenBradleyCip\Page\AllenBradleyCipTcpDebugPage.razor" Link="Pages\ABCIP\AllenBradleyCipTcpDebugPage.razor" />
|
||||
<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.AllenBradleyCip\ThingsGateway.Foundation.Adapter.AllenBradleyCip.csproj" />
|
||||
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsTcpDebugPage.razor.cs" Link="Pages\OmronFins\OmronFinsTcpDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsUdpDebugPage.razor.cs" Link="Pages\OmronFins\OmronFinsUdpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsTcpDebugPage.razor" Link="Pages\OmronFins\OmronFinsTcpDebugPage.razor" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsUdpDebugPage.razor" Link="Pages\OmronFins\OmronFinsUdpDebugPage.razor" />
|
||||
<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Omron\ThingsGateway.Foundation.Adapter.Omron.csproj" />
|
||||
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Secs\Page\SecsHsmsTcpDebugPage.razor.cs" Link="Pages\Secs\SecsHsmsTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Secs\Page\SecsHsmsTcpDebugPage.razor" Link="Pages\Secs\SecsHsmsTcpDebugPage.razor" />
|
||||
<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Secs\ThingsGateway.Foundation.Adapter.Secs.csproj" />
|
||||
|
||||
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.TS550\Page\TS550DebugPage.razor.cs" Link="Pages\TS550\TS550DebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.TS550\Page\TS550DebugPage.razor" Link="Pages\TS550\TS550DebugPage.razor" />
|
||||
<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.TS550\ThingsGateway.Foundation.Adapter.TS550.csproj" />
|
||||
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialDebugPage.razor.cs" Link="Pages\Vigor\VigorSerialDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialOverTcpDebugPage.razor.cs" Link="Pages\Vigor\VigorSerialOverTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialDebugPage.razor" Link="Pages\Vigor\VigorSerialDebugPage.razor" />
|
||||
<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialOverTcpDebugPage.razor" Link="Pages\Vigor\VigorSerialOverTcpDebugPage.razor" />
|
||||
<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Vigor\ThingsGateway.Foundation.Adapter.Vigor.csproj" />
|
||||
|
||||
|
||||
<Compile Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialDebugPage.razor.cs" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialOverTcpDebugPage.razor.cs" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialOverTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialDebugPage.razor" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialDebugPage.razor" />
|
||||
<Content Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialOverTcpDebugPage.razor" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialOverTcpDebugPage.razor" />
|
||||
<ProjectReference Include="..\..\PluginProAF2021\ThingsGateway.Foundation.Adapter.HZW_QTJC_01\ThingsGateway.Foundation.Adapter.HZW_QTJC_01.csproj" />
|
||||
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007DebugPage.razor.cs" Link="Pages\DLT645\DLT645_2007DebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\MqttRpcNameVaueWithId.cs" Link="Pages\Mqtt\MqttRpcNameVaueWithId.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientDebugPage.razor.cs" Link="Pages\Mqtt\MqttClientDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientPage.razor.cs" Link="Pages\Mqtt\MqttClientPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\PrivateLogger.cs" Link="Pages\Mqtt\PrivateLogger.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcClient.cs" Link="Pages\Mqtt\MqttRpcClient.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcClientExtensions.cs" Link="Pages\Mqtt\MqttRpcClientExtensions.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcTopicPair.cs" Link="Pages\Mqtt\MqttRpcTopicPair.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007DebugPage.razor" Link="Pages\DLT645\DLT645_2007DebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007OverTcpDebugPage.razor.cs" Link="Pages\DLT645\DLT645_2007OverTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007OverTcpDebugPage.razor" Link="Pages\DLT645\DLT645_2007OverTcpDebugPage.razor" />
|
||||
|
||||
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuDebugPage.razor.cs" Link="Pages\Modbus\ModbusRtuDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuDebugPage.razor" Link="Pages\Modbus\ModbusRtuDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverTcpDebugPage.razor.cs" Link="Pages\Modbus\ModbusRtuOverTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverTcpDebugPage.razor" Link="Pages\Modbus\ModbusRtuOverTcpDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverUdpDebugPage.razor.cs" Link="Pages\Modbus\ModbusRtuOverUdpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverUdpDebugPage.razor" Link="Pages\Modbus\ModbusRtuOverUdpDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusSerialServerDebugPage.razor.cs" Link="Pages\Modbus\ModbusSerialServerDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusSerialServerDebugPage.razor" Link="Pages\Modbus\ModbusSerialServerDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDebugPage.razor.cs" Link="Pages\Modbus\ModbusTcpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDebugPage.razor" Link="Pages\Modbus\ModbusTcpDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDtuDebugPage.razor.cs" Link="Pages\Modbus\ModbusTcpDtuDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDtuDebugPage.razor" Link="Pages\Modbus\ModbusTcpDtuDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpServerDebugPage.razor.cs" Link="Pages\Modbus\ModbusTcpServerDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpServerDebugPage.razor" Link="Pages\Modbus\ModbusTcpServerDebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusUdpDebugPage.razor.cs" Link="Pages\Modbus\ModbusUdpDebugPage.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusUdpDebugPage.razor" Link="Pages\Modbus\ModbusUdpDebugPage.razor" />
|
||||
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientDebugPage.razor.cs" Link="Pages\OPCDA\OPCDAClientDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientPage.razor.cs" Link="Pages\OPCDA\OPCDAClientPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAImportVariable.razor.cs" Link="Pages\OPCDA\OPCDAImportVariable.razor.cs" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientDebugPage.razor" Link="Pages\OPCDA\OPCDAClientDebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientPage.razor" Link="Pages\OPCDA\OPCDAClientPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAImportVariable.razor" Link="Pages\OPCDA\OPCDAImportVariable.razor" />
|
||||
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientDebugPage.razor" Link="Pages\OPCUA\OPCUAClientDebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientPage.razor" Link="Pages\OPCUA\OPCUAClientPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAImportVariable.razor" Link="Pages\OPCUA\OPCUAImportVariable.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientDebugPage.razor.cs" Link="Pages\OPCUA\OPCUAClientDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientPage.razor.cs" Link="Pages\OPCUA\OPCUAClientPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAImportVariable.razor.cs" Link="Pages\OPCUA\OPCUAImportVariable.razor.cs" />
|
||||
|
||||
|
||||
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1200DebugPage.razor" Link="Pages\Siemens\S7_1200DebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1500DebugPage.razor" Link="Pages\Siemens\S7_1500DebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200DebugPage.razor" Link="Pages\Siemens\S7_200DebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200SMARTDebugPage.razor" Link="Pages\Siemens\S7_200SMARTDebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_300DebugPage.razor" Link="Pages\Siemens\S7_300DebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_400DebugPage.razor" Link="Pages\Siemens\S7_400DebugPage.razor" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1200DebugPage.razor.cs" Link="Pages\Siemens\S7_1200DebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1500DebugPage.razor.cs" Link="Pages\Siemens\S7_1500DebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200DebugPage.razor.cs" Link="Pages\Siemens\S7_200DebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200SMARTDebugPage.razor.cs" Link="Pages\Siemens\S7_200SMARTDebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_300DebugPage.razor.cs" Link="Pages\Siemens\S7_300DebugPage.razor.cs" />
|
||||
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_400DebugPage.razor.cs" Link="Pages\Siemens\S7_400DebugPage.razor.cs" />
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup >
|
||||
<!--<PackageReference Include="ThingsGateway.Foundation.Adapter.DLT645" Version="*" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Adapter.Modbus" Version="*" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Adapter.OPCDA" Version="*" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Adapter.OPCUA" Version="*" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Adapter.Siemens" Version="*" />-->
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.DLT645\ThingsGateway.Foundation.Adapter.DLT645.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.Modbus\ThingsGateway.Foundation.Adapter.Modbus.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.OPCDA\ThingsGateway.Foundation.Adapter.OPCDA.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.OPCUA\ThingsGateway.Foundation.Adapter.OPCUA.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.Siemens\ThingsGateway.Foundation.Adapter.Siemens.csproj" />
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup >
|
||||
<ProjectReference Include="..\..\Web\ThingsGateway.Components\ThingsGateway.Components.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientDebugPage.razor" Link="Pages\Mqtt\MqttClientDebugPage.razor" />
|
||||
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientPage.razor" Link="Pages\Mqtt\MqttClientPage.razor" />
|
||||
<PackageReference Include="MQTTnet" Version="4.3.1.873" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
@@ -0,0 +1,29 @@
|
||||
@*
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
*@
|
||||
|
||||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using BlazorComponent
|
||||
@using Masa.Blazor
|
||||
@using Masa.Blazor.Presets
|
||||
@using ThingsGateway.Foundation.Core;
|
||||
@using ThingsGateway.Components;
|
||||
@using ThingsGateway.Core;
|
||||
@using System.Net.Http.Json
|
||||
@using System.IO;
|
||||
@using System.Text.Json;
|
||||
@using ThingsGateway.Foundation.Serial;
|
||||
@using ThingsGateway.Foundation.Sockets;
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>ThingsGateway.Foundation.Demo</title>
|
||||
<base href="/" />
|
||||
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
|
||||
|
||||
<link href="_content/Masa.Blazor/css/masa-blazor.min.css" rel="stylesheet" />
|
||||
<link href="_content/ThingsGateway.Components/css/materialdesign/v7.1.96/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="_content/ThingsGateway.Components/css/material/icons.css" rel="stylesheet">
|
||||
<link href="_content/ThingsGateway.Components/css/fontawesome/v6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="_content/ThingsGateway.Components/style/custom.css" rel="stylesheet">
|
||||
<link href="_content/ThingsGateway.Components/prism/prism-material-dark-for-masa.css" rel="stylesheet">
|
||||
<link href="_content/ThingsGateway.Components/prism/prism-line-highlight.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<span>
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
</span>
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.webview.js" autostart="true"></script>
|
||||
<script src="_content/ThingsGateway.Components/prism/prism.min.js"></script>
|
||||
<script src="_content/BlazorComponent/js/blazor-component.js"></script>
|
||||
</body>
|
||||
</html>
|
@@ -1,7 +1,12 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Version>2.0.0.0</Version>
|
||||
<Version>3.0.0.20</Version>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFrameworks>net45;netstandard2.0;net6.0;net7.0</TargetFrameworks>
|
||||
<Description>
|
||||
ThingsGateway.Foundation是工业设备通讯类库,归属于ThingsGateway边缘网关项目,说明文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
</Description>
|
||||
<Authors>Diego</Authors>
|
||||
<Product>ThingsGateway</Product>
|
||||
<Copyright>© 2023-present Diego</Copyright>
|
||||
@@ -11,38 +16,38 @@
|
||||
<EmbedAllSources>true</EmbedAllSources>
|
||||
<RepositoryType>Gitee</RepositoryType>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageReadmeFile>./README.md</PackageReadmeFile>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://diego2098.gitee.io/thingsgateway-docs/</PackageProjectUrl>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageTags>ThingsGateway;Diego;dotNET China;Blazor;设备采集;边缘网关</PackageTags>
|
||||
<PackageOutputPath>../../../nupkgs</PackageOutputPath>
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<DelaySign>False</DelaySign>
|
||||
<AssemblyOriginatorKeyFile>..\..\..\..\snks\ThingsGateway.snk</AssemblyOriginatorKeyFile>
|
||||
<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
|
||||
<PackageOutputPath>../../nupkgs</PackageOutputPath>
|
||||
<AssemblyOriginatorKeyFile>../../../snks/ThingsGateway.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\..\README.md" Pack="true" PackagePath="\" />
|
||||
<None Include="..\..\..\README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\..\icon.png">
|
||||
<None Include="..\..\..\icon.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
<DebugType>Embedded</DebugType>
|
||||
<EmbedAllSources>True</EmbedAllSources>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@@ -1,4 +1,4 @@
|
||||
#region copyright
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
@@ -10,38 +10,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
|
||||
/// <summary>
|
||||
/// 资源枚举
|
||||
/// 控制码
|
||||
/// </summary>
|
||||
public enum ThingsGatewayStatus : byte
|
||||
public enum ControlCode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据转换失败
|
||||
/// 读数据
|
||||
/// </summary>
|
||||
[Description("数据转换失败")]
|
||||
DataTransError,
|
||||
Read = 0x11,
|
||||
/// <summary>
|
||||
/// 接收
|
||||
/// 读后续数据
|
||||
/// </summary>
|
||||
[Description("接收")]
|
||||
Received,
|
||||
ReadSub = 0x12,
|
||||
/// <summary>
|
||||
/// 发送
|
||||
/// 读站号
|
||||
/// </summary>
|
||||
[Description("发送")]
|
||||
Send,
|
||||
ReadStation = 0x13,
|
||||
/// <summary>
|
||||
/// 原始字节数据
|
||||
/// 写数据
|
||||
/// </summary>
|
||||
[Description("原始字节数据")]
|
||||
SourceHexData,
|
||||
Write = 0x14,
|
||||
/// <summary>
|
||||
/// 未知错误
|
||||
/// 写站号
|
||||
/// </summary>
|
||||
[Description("未知错误")]
|
||||
UnknownError,
|
||||
WriteStation = 0x15,
|
||||
/// <summary>
|
||||
/// 广播校时
|
||||
/// </summary>
|
||||
BroadcastTime = 0x08,
|
||||
/// <summary>
|
||||
/// 冻结
|
||||
/// </summary>
|
||||
Freeze = 0x16,
|
||||
/// <summary>
|
||||
/// 更新波特率
|
||||
/// </summary>
|
||||
WriteBaudRate = 0x17,
|
||||
/// <summary>
|
||||
/// 更新密码
|
||||
/// </summary>
|
||||
WritePassword = 0x18,
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,480 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
/// <summary>
|
||||
/// DLT645_2007
|
||||
/// </summary>
|
||||
public class DLT645_2007 : ReadWriteDevicesSerialSessionBase
|
||||
{
|
||||
/// <summary>
|
||||
/// DLT645_2007
|
||||
/// </summary>
|
||||
/// <param name="serialSession"></param>
|
||||
public DLT645_2007(SerialSession serialSession) : base(serialSession)
|
||||
{
|
||||
ThingsGatewayBitConverter = new DLT645_2007BitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加FE FE FE FE的报文头部
|
||||
/// </summary>
|
||||
[Description("前导符报文头")]
|
||||
public bool EnableFEHead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入需操作员代码
|
||||
/// </summary>
|
||||
[Description("操作员代码")]
|
||||
public string OperCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入密码
|
||||
/// </summary>
|
||||
[Description("写入密码")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通讯地址BCD码,一般应该是12个字符
|
||||
/// </summary>
|
||||
[Description("通讯地址")]
|
||||
public string Station { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
|
||||
var str = """
|
||||
查看附带文档或者相关资料,下面列举一下常见的数据标识地址
|
||||
|
||||
地址 说明
|
||||
-----------------------------------------
|
||||
02010100 A相电压
|
||||
02020100 A相电流
|
||||
02030000 瞬时总有功功率
|
||||
00000000 (当前)组合有功总电能
|
||||
00010000 (当前)正向有功总电能
|
||||
|
||||
""";
|
||||
return base.GetAddressDescription() + Environment.NewLine + str;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
var dataHandleAdapter = new DLT645_2007DataHandleAdapter
|
||||
{
|
||||
EnableFEHead = EnableFEHead
|
||||
};
|
||||
SerialSession.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, string value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
Password ??= string.Empty;
|
||||
OperCode ??= string.Empty;
|
||||
if (Password.Length < 8)
|
||||
Password = Password.PadLeft(8, '0');
|
||||
if (OperCode.Length < 8)
|
||||
OperCode = OperCode.PadLeft(8, '0');
|
||||
var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
|
||||
string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, string value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
Password ??= string.Empty;
|
||||
OperCode ??= string.Empty;
|
||||
if (Password.Length < 8)
|
||||
Password = Password.PadLeft(8, '0');
|
||||
if (OperCode.Length < 8)
|
||||
OperCode = OperCode.PadLeft(8, '0');
|
||||
var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
|
||||
string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, uint value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, double value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, float value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, long value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, ulong value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, ushort value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, short value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, int value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
|
||||
|
||||
#region 其他方法
|
||||
|
||||
/// <summary>
|
||||
/// 广播校时
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public OperResult BroadcastTime(DateTime dateTime, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
string str = $"{dateTime.Second:D2}{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}{dateTime.Year % 100:D2}";
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.BroadcastTime, str.ByHexStringToBytes().ToArray(), "999999999999".ByHexStringToBytes());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
SerialSession.Send(commandResult.Content);
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻结
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> FreezeAsync(DateTime dateTime, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
string str = $"{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}";
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.Freeze, str.ByHexStringToBytes().ToArray(), Station.ByHexStringToBytes().Reverse().ToArray());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取通信地址
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult<string>> ReadDeviceStationAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.ReadStation, null, "AAAAAAAAAAAA".ByHexStringToBytes());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
var buffer = result1.Content.SelectMiddle(0, 6).BytesAdd(-0x33);
|
||||
return OperResult.CreateSuccessResult(buffer.Reverse().ToArray().ToHexString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<string>(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<string>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 修改波特率
|
||||
/// </summary>
|
||||
/// <param name="baudRate"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> WriteBaudRateAsync(int baudRate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
byte baudRateByte;
|
||||
switch (baudRate)
|
||||
{
|
||||
case 600: baudRateByte = 0x02; break;
|
||||
case 1200: baudRateByte = 0x04; break;
|
||||
case 2400: baudRateByte = 0x08; break;
|
||||
case 4800: baudRateByte = 0x10; break;
|
||||
case 9600: baudRateByte = 0x20; break;
|
||||
case 19200: baudRateByte = 0x40; break;
|
||||
default: return new OperResult<string>($"不支持此波特率:{baudRate}");
|
||||
}
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteBaudRate, new byte[] { baudRateByte }, Station.ByHexStringToBytes().Reverse().ToArray());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新通信地址
|
||||
/// </summary>
|
||||
/// <param name="station"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> WriteDeviceStationAsync(string station, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteStation, station.ByHexStringToBytes().Reverse().ToArray(), "AAAAAAAAAAAA".ByHexStringToBytes());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 修改密码
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <param name="oldPassword"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> WritePasswordAsync(byte level, string oldPassword, string newPassword, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
string str = $"04000C{level + 1:D2}";
|
||||
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WritePassword,
|
||||
str.ByHexStringToBytes().Reverse().ToArray()
|
||||
.SpliceArray(oldPassword.ByHexStringToBytes().Reverse().ToArray())
|
||||
.SpliceArray(newPassword.ByHexStringToBytes().Reverse().ToArray())
|
||||
, Station.ByHexStringToBytes().Reverse().ToArray());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
|
||||
/// <summary>
|
||||
/// DLT645_2007Address
|
||||
/// </summary>
|
||||
public class DLT645_2007Address : DeviceAddressBase
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public DLT645_2007Address()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据标识
|
||||
/// </summary>
|
||||
public byte[] DataId { get; set; } = new byte[0];
|
||||
/// <summary>
|
||||
/// 反转解析
|
||||
/// </summary>
|
||||
public bool Reverse { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 站号信息
|
||||
/// </summary>
|
||||
public byte[] Station { get; set; } = new byte[0];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 解析地址
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <returns></returns>
|
||||
public static DLT645_2007Address ParseFrom(string address)
|
||||
{
|
||||
DLT645_2007Address dLT645_2007Address = new();
|
||||
byte[] array;
|
||||
array = new byte[0];
|
||||
if (address.IndexOf(';') < 0)
|
||||
{
|
||||
array = address.ByHexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] strArray = address.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (int index = 0; index < strArray.Length; ++index)
|
||||
{
|
||||
if (strArray[index].ToUpper().StartsWith("S="))
|
||||
{
|
||||
var station = strArray[index].Substring(2);
|
||||
if (station.IsNullOrEmpty()) station = string.Empty;
|
||||
if (station.Length < 12)
|
||||
station = station.PadLeft(12, '0');
|
||||
dLT645_2007Address.Station = station.ByHexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
else if (strArray[index].Contains("r="))
|
||||
{
|
||||
dLT645_2007Address.Reverse = strArray[index].Substring(2).GetBoolValue();
|
||||
}
|
||||
else if (!strArray[index].Contains("="))
|
||||
{
|
||||
array = strArray[index].ByHexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
dLT645_2007Address.DataId = array;
|
||||
return dLT645_2007Address;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringGeter = new();
|
||||
if (Station.Length > 0)
|
||||
{
|
||||
stringGeter.Append($"s={Station.Reverse().ToArray().ToHexString()};");
|
||||
}
|
||||
if (DataId.Length > 0)
|
||||
{
|
||||
stringGeter.Append($"{DataId.Reverse().ToArray().ToHexString()};");
|
||||
}
|
||||
if (!Reverse)
|
||||
{
|
||||
stringGeter.Append($"s={Reverse.ToString()};");
|
||||
}
|
||||
return stringGeter.ToString();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
/// <summary>
|
||||
/// DLT645_2007
|
||||
/// </summary>
|
||||
public class DLT645_2007BitConverter : ThingsGatewayBitConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// DLT645_2007
|
||||
/// </summary>
|
||||
public DLT645_2007BitConverter(EndianType endianType) : base(endianType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DLT645协议转换double
|
||||
/// </summary>
|
||||
/// <param name="buffer">带数据项标识</param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public override double ToDouble(byte[] buffer, int offset)
|
||||
{
|
||||
return Convert.ToDouble(this.ToString(buffer, offset, buffer.Length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IThingsGatewayBitConverter CopyNew()
|
||||
{
|
||||
return new DLT645_2007BitConverter(EndianType)
|
||||
{
|
||||
DataFormat = DataFormat,
|
||||
BcdFormat = BcdFormat,
|
||||
Encoding = Encoding,
|
||||
IsStringReverseByteWord = IsStringReverseByteWord,
|
||||
Length = Length,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString(byte[] buffer)
|
||||
{
|
||||
return this.ToString(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString(byte[] buffer, int offset, int length)
|
||||
{
|
||||
buffer = buffer.RemoveBegin(offset);
|
||||
buffer = buffer.BytesAdd(-0x33);
|
||||
var dataInfos = DLT645Helper.GetDataInfos(buffer);
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (var dataInfo in dataInfos)
|
||||
{
|
||||
//实际数据
|
||||
var content = buffer.SelectMiddle(4, dataInfo.ByteLength).Reverse().ToArray();
|
||||
if (dataInfo.IsSigned)//可能为负数
|
||||
{
|
||||
if (content[0] > 0x80)//最高位是表示正负
|
||||
{
|
||||
content[0] = (byte)(content[0] - 0x80);
|
||||
if (dataInfo.Digtal == 0)//无小数点
|
||||
{
|
||||
stringBuilder.Append($"-{content.ToHexString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append((-(Convert.ToDouble(content.ToHexString()) / Math.Pow(10.0, dataInfo.Digtal))).ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ToString(stringBuilder, dataInfo, content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ToString(stringBuilder, dataInfo, content);
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
|
||||
static void ToString(StringBuilder stringBuilder, DataInfo dataInfo, byte[] content)
|
||||
{
|
||||
if (dataInfo.Digtal < 0)
|
||||
{
|
||||
stringBuilder.Append($"{Encoding.ASCII.GetString(content)}");
|
||||
}
|
||||
else if (dataInfo.Digtal == 0)//无小数点
|
||||
{
|
||||
stringBuilder.Append($"{content.ToHexString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(((Convert.ToDouble(content.ToHexString()) / Math.Pow(10.0, dataInfo.Digtal))).ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,191 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
|
||||
/// <summary>
|
||||
/// DLT645_2007DataHandleAdapter
|
||||
/// </summary>
|
||||
public class DLT645_2007DataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<DLT645_2007Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加FE FE FE FE的报文头部
|
||||
/// </summary>
|
||||
[Description("前导符报文头")]
|
||||
public bool EnableFEHead { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override byte[] PackCommand(byte[] command)
|
||||
{
|
||||
//打包时加上4个FE字节
|
||||
if (EnableFEHead)
|
||||
{
|
||||
return DataTransUtil.SpliceArray(new byte[4] { 0xFE, 0xFE, 0xFE, 0xFE }, command);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override DLT645_2007Message GetInstance()
|
||||
{
|
||||
return new DLT645_2007Message();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override FilterResult UnpackResponse(DLT645_2007Message request, byte[] send, byte[] body, byte[] response)
|
||||
{
|
||||
//因为设备可能带有FE前导符开头,这里找到0x68的位置
|
||||
int headCodeIndex = -1;
|
||||
if (response != null)
|
||||
{
|
||||
for (int index = 0; index < response.Length; index++)
|
||||
{
|
||||
if (response[index] == 0x68)
|
||||
{
|
||||
headCodeIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int sendHeadCodeIndex = 0;
|
||||
if (send != null)
|
||||
{
|
||||
for (int index = 0; index < send.Length; index++)
|
||||
{
|
||||
if (send[index] == 0x68)
|
||||
{
|
||||
sendHeadCodeIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//帧起始符 地址域 帧起始符 控制码 数据域长度共10个字节
|
||||
if (headCodeIndex < 0 || headCodeIndex + 10 > response.Length)
|
||||
return FilterResult.Cache;
|
||||
|
||||
|
||||
var len = 10 + response[headCodeIndex + 9] + 2;
|
||||
|
||||
if (response.Length - headCodeIndex < len)
|
||||
{
|
||||
return FilterResult.Cache;
|
||||
}
|
||||
if (response.Length - headCodeIndex >= len && response[len + headCodeIndex - 1] == 0x16)
|
||||
{
|
||||
|
||||
//检查校验码
|
||||
int sumCheck = 0;
|
||||
for (int i = headCodeIndex; i < len + headCodeIndex - 2; i++)
|
||||
sumCheck += response[i];
|
||||
if ((byte)sumCheck != response[len + headCodeIndex - 2])
|
||||
{
|
||||
//校验错误
|
||||
request.Message = "和校验错误";
|
||||
request.ErrorCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
if (
|
||||
(response[headCodeIndex + 1] != send[sendHeadCodeIndex + 1]) ||
|
||||
(response[headCodeIndex + 2] != send[sendHeadCodeIndex + 2]) ||
|
||||
(response[headCodeIndex + 3] != send[sendHeadCodeIndex + 3]) ||
|
||||
(response[headCodeIndex + 4] != send[sendHeadCodeIndex + 4]) ||
|
||||
(response[headCodeIndex + 5] != send[sendHeadCodeIndex + 5]) ||
|
||||
(response[headCodeIndex + 6] != send[sendHeadCodeIndex + 6])
|
||||
)//设备地址不符合时,返回错误
|
||||
{
|
||||
|
||||
if (
|
||||
(send[sendHeadCodeIndex + 1] == 0xAA) &&
|
||||
(send[sendHeadCodeIndex + 2] == 0xAA) &&
|
||||
(send[sendHeadCodeIndex + 3] == 0xAA) &&
|
||||
(send[sendHeadCodeIndex + 4] == 0xAA) &&
|
||||
(send[sendHeadCodeIndex + 5] == 0xAA) &&
|
||||
(send[sendHeadCodeIndex + 6] == 0xAA)
|
||||
)//读写通讯地址例外
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Message = "返回地址不符合规则";
|
||||
request.ErrorCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ((response[headCodeIndex + 8] != send[sendHeadCodeIndex + 8] + 0x80))//控制码不符合时,返回错误
|
||||
{
|
||||
request.Message = $"返回控制码:0x{response[headCodeIndex + 8]:X2},请求控制码:0x{send[sendHeadCodeIndex + 8]:X2},不符合规则";
|
||||
request.ErrorCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if ((response[headCodeIndex + 8] & 0x40) == 0x40)//控制码bit6为1时,返回错误
|
||||
{
|
||||
byte byte1 = (byte)(response[headCodeIndex + 10] - 0x33);
|
||||
var error = DLT645Helper.Get2007ErrorMessage(byte1);
|
||||
request.Message = $"异常控制码:0x{response[headCodeIndex + 8]:X2},错误信息:{error}";
|
||||
request.ErrorCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
if (send[sendHeadCodeIndex + 8] == (byte)ControlCode.Read ||
|
||||
send[sendHeadCodeIndex + 8] == (byte)ControlCode.Write
|
||||
)
|
||||
{
|
||||
//数据标识不符合时,返回错误
|
||||
if (
|
||||
(response[headCodeIndex + 10] == send[sendHeadCodeIndex + 10]) &&
|
||||
(response[headCodeIndex + 11] == send[sendHeadCodeIndex + 11]) &&
|
||||
(response[headCodeIndex + 12] == send[sendHeadCodeIndex + 12]) &&
|
||||
(response[headCodeIndex + 13] == send[sendHeadCodeIndex + 13])
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Message = "返回数据标识不符合规则";
|
||||
request.ErrorCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
request.Content = response.RemoveBegin(headCodeIndex + 10).RemoveLast(response.Length + 2 - len - headCodeIndex);
|
||||
request.ErrorCode = 0;
|
||||
return FilterResult.Success;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
request.ErrorCode = 999;
|
||||
return FilterResult.Success;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class DLT645_2007Message : MessageBase, IMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => -1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[] heads)
|
||||
{
|
||||
BodyLength = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,480 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
/// <summary>
|
||||
/// DLT645_2007
|
||||
/// </summary>
|
||||
public class DLT645_2007OverTcp : ReadWriteDevicesTcpClientBase
|
||||
{
|
||||
/// <summary>
|
||||
/// DLT645_2007
|
||||
/// </summary>
|
||||
/// <param name="tcpClient"></param>
|
||||
public DLT645_2007OverTcp(TcpClient tcpClient) : base(tcpClient)
|
||||
{
|
||||
ThingsGatewayBitConverter = new DLT645_2007BitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加FE FE FE FE的报文头部
|
||||
/// </summary>
|
||||
[Description("前导符报文头")]
|
||||
public bool EnableFEHead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入需操作员代码
|
||||
/// </summary>
|
||||
[Description("操作员代码")]
|
||||
public string OperCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入密码
|
||||
/// </summary>
|
||||
[Description("写入密码")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通讯地址BCD码,一般应该是12个字符
|
||||
/// </summary>
|
||||
[Description("通讯地址")]
|
||||
public string Station { get; set; }
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
|
||||
var str = """
|
||||
查看附带文档或者相关资料,下面列举一下常见的数据标识地址
|
||||
|
||||
地址 说明
|
||||
-----------------------------------------
|
||||
02010100 A相电压
|
||||
02020100 A相电流
|
||||
02030000 瞬时总有功功率
|
||||
00000000 (当前)组合有功总电能
|
||||
00010000 (当前)正向有功总电能
|
||||
|
||||
""";
|
||||
return base.GetAddressDescription() + Environment.NewLine + str;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
var dataHandleAdapter = new DLT645_2007DataHandleAdapter
|
||||
{
|
||||
EnableFEHead = EnableFEHead
|
||||
};
|
||||
TcpClient.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, string value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
Password ??= string.Empty;
|
||||
OperCode ??= string.Empty;
|
||||
if (Password.Length < 8)
|
||||
Password = Password.PadLeft(8, '0');
|
||||
if (OperCode.Length < 8)
|
||||
OperCode = OperCode.PadLeft(8, '0');
|
||||
var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
|
||||
string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, string value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
Password ??= string.Empty;
|
||||
OperCode ??= string.Empty;
|
||||
if (Password.Length < 8)
|
||||
Password = Password.PadLeft(8, '0');
|
||||
if (OperCode.Length < 8)
|
||||
OperCode = OperCode.PadLeft(8, '0');
|
||||
var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
|
||||
string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, uint value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, double value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, float value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, long value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, ulong value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, ushort value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, short value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, int value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
|
||||
|
||||
|
||||
#region 其他方法
|
||||
|
||||
/// <summary>
|
||||
/// 广播校时
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public OperResult BroadcastTime(DateTime dateTime, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
string str = $"{dateTime.Second:D2}{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}{dateTime.Year % 100:D2}";
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.BroadcastTime, str.ByHexStringToBytes().Reverse().ToArray(), "999999999999".ByHexStringToBytes());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
TcpClient.Send(commandResult.Content);
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻结
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> FreezeAsync(DateTime dateTime, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
string str = $"{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}";
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.Freeze, str.ByHexStringToBytes().Reverse().ToArray(), Station.ByHexStringToBytes().Reverse().ToArray());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取通信地址
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult<string>> ReadDeviceStationAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.ReadStation, null, "AAAAAAAAAAAA".ByHexStringToBytes());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
var buffer = result1.Content.SelectMiddle(0, 6).BytesAdd(-0x33);
|
||||
return OperResult.CreateSuccessResult(buffer.Reverse().ToArray().ToHexString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<string>(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<string>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 修改波特率
|
||||
/// </summary>
|
||||
/// <param name="baudRate"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> WriteBaudRateAsync(int baudRate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
byte baudRateByte;
|
||||
switch (baudRate)
|
||||
{
|
||||
case 600: baudRateByte = 0x02; break;
|
||||
case 1200: baudRateByte = 0x04; break;
|
||||
case 2400: baudRateByte = 0x08; break;
|
||||
case 4800: baudRateByte = 0x10; break;
|
||||
case 9600: baudRateByte = 0x20; break;
|
||||
case 19200: baudRateByte = 0x40; break;
|
||||
default: return new OperResult<string>($"不支持此波特率:{baudRate}");
|
||||
}
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteBaudRate, new byte[] { baudRateByte }, Station.ByHexStringToBytes().Reverse().ToArray());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新通信地址
|
||||
/// </summary>
|
||||
/// <param name="station"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> WriteDeviceStationAsync(string station, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteStation, station.ByHexStringToBytes().Reverse().ToArray(), "AAAAAAAAAAAA".ByHexStringToBytes());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 修改密码
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <param name="oldPassword"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperResult> WritePasswordAsync(byte level, string oldPassword, string newPassword, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
string str = $"04000C{level:D2}";
|
||||
|
||||
var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WritePassword,
|
||||
str.ByHexStringToBytes().Reverse().ToArray()
|
||||
.SpliceArray(oldPassword.ByHexStringToBytes().Reverse().ToArray())
|
||||
.SpliceArray(newPassword.ByHexStringToBytes().Reverse().ToArray())
|
||||
, Station.ByHexStringToBytes().Reverse().ToArray());
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
|
||||
var result1 = ((MessageBase)result.RequestInfo);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<string>(ex);
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
|
||||
|
||||
internal static class PackHelper
|
||||
{
|
||||
public static List<T> LoadSourceRead<T, T2>(IReadWrite device, List<T2> deviceVariables, int maxPack) where T : IDeviceVariableSourceRead<IDeviceVariableRunTime>, new() where T2 : IDeviceVariableRunTime, new()
|
||||
{
|
||||
var byteConverter = device.ThingsGatewayBitConverter;
|
||||
var result = new List<T>();
|
||||
//需要先剔除额外信息,比如dataformat等
|
||||
foreach (var item in deviceVariables)
|
||||
{
|
||||
var address = item.VariableAddress;
|
||||
|
||||
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, byteConverter);
|
||||
item.ThingsGatewayBitConverter = transformParameter;
|
||||
//item.VariableAddress = address;
|
||||
item.Index = device.GetBitOffset(item.VariableAddress);
|
||||
|
||||
result.Add(new()
|
||||
{
|
||||
DeviceVariableRunTimes = new() { item },
|
||||
VariableAddress = address,
|
||||
Length = 1,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
global using ThingsGateway.Foundation.Core;
|
||||
global using ThingsGateway.Foundation.Serial;
|
||||
global using ThingsGateway.Foundation.Sockets;
|
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -0,0 +1,21 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
global using ThingsGateway.Foundation.Core;
|
||||
global using ThingsGateway.Foundation.Serial;
|
||||
global using ThingsGateway.Foundation.Sockets;
|
@@ -12,6 +12,9 @@
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <summary>
|
||||
@@ -26,22 +29,10 @@ public class ModbusAddress : DeviceAddressBase
|
||||
{
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public ModbusAddress(string address, ushort len)
|
||||
{
|
||||
Station = 0;
|
||||
AddressStart = 0;
|
||||
Parse(address, len);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ModbusAddress(string address, byte station)
|
||||
{
|
||||
Station = station;
|
||||
AddressStart = 0;
|
||||
Parse(address, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取功能码
|
||||
/// </summary>
|
||||
public ushort AddressStart => Address.ToUShort();
|
||||
/// <summary>
|
||||
/// 读取功能码
|
||||
/// </summary>
|
||||
@@ -56,15 +47,44 @@ public class ModbusAddress : DeviceAddressBase
|
||||
/// 写入功能码
|
||||
/// </summary>
|
||||
public byte WriteFunction { get; set; }
|
||||
/// <summary>
|
||||
/// 打包临时写入,需要读取的字节长度
|
||||
/// </summary>
|
||||
public int ByteLength { get; set; }
|
||||
/// <summary>
|
||||
/// BitIndex
|
||||
/// </summary>
|
||||
public int BitIndex => (int)(Address.SplitDot().LastOrDefault().ToInt());
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Parse(string address, int length)
|
||||
/// <summary>
|
||||
/// 读取功能码
|
||||
/// </summary>
|
||||
public ushort AddressEnd => (ushort)(AddressStart + (Math.Ceiling(ByteLength / 2.0) > 0 ? Math.Ceiling(ByteLength / 2.0) : 1));
|
||||
|
||||
/// <summary>
|
||||
/// 作为Slave时需提供的SocketId,用于分辨Socket客户端,通常对比的是初始链接时的注册包
|
||||
/// </summary>
|
||||
public string SocketId { get; set; }
|
||||
/// <summary>
|
||||
/// 解析地址
|
||||
/// </summary>
|
||||
public static ModbusAddress ParseFrom(string address, byte station)
|
||||
{
|
||||
Length = length;
|
||||
ModbusAddress modbusAddress = new()
|
||||
{
|
||||
Station = station
|
||||
};
|
||||
return ParseFrom(address, modbusAddress);
|
||||
}
|
||||
/// <summary>
|
||||
/// 解析地址
|
||||
/// </summary>
|
||||
public static ModbusAddress ParseFrom(string address, ModbusAddress modbusAddress = null)
|
||||
{
|
||||
modbusAddress ??= new();
|
||||
if (address.IndexOf(';') < 0)
|
||||
{
|
||||
Address(address);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -74,12 +94,16 @@ public class ModbusAddress : DeviceAddressBase
|
||||
if (strArray[index].ToUpper().StartsWith("S="))
|
||||
{
|
||||
if (Convert.ToInt16(strArray[index].Substring(2)) > 0)
|
||||
Station = byte.Parse(strArray[index].Substring(2));
|
||||
modbusAddress.Station = byte.Parse(strArray[index].Substring(2));
|
||||
}
|
||||
else if (strArray[index].ToUpper().StartsWith("W="))
|
||||
{
|
||||
if (Convert.ToInt16(strArray[index].Substring(2)) > 0)
|
||||
this.WriteFunction = byte.Parse(strArray[index].Substring(2));
|
||||
modbusAddress.WriteFunction = byte.Parse(strArray[index].Substring(2));
|
||||
}
|
||||
else if (strArray[index].ToUpper().StartsWith("ID="))
|
||||
{
|
||||
modbusAddress.SocketId = strArray[index].Substring(3);
|
||||
}
|
||||
else if (!strArray[index].Contains("="))
|
||||
{
|
||||
@@ -88,51 +112,55 @@ public class ModbusAddress : DeviceAddressBase
|
||||
}
|
||||
}
|
||||
|
||||
return modbusAddress;
|
||||
|
||||
void Address(string address)
|
||||
{
|
||||
var readF = ushort.Parse(address.Substring(0, 1));
|
||||
if (readF > 4)
|
||||
throw new("功能码错误");
|
||||
GetFunction(readF);
|
||||
AddressStart = int.Parse(address.Substring(1)) - 1;
|
||||
switch (readF)
|
||||
{
|
||||
case 0:
|
||||
modbusAddress.ReadFunction = 1;
|
||||
break;
|
||||
case 1:
|
||||
modbusAddress.ReadFunction = 2;
|
||||
break;
|
||||
case 3:
|
||||
modbusAddress.ReadFunction = 4;
|
||||
break;
|
||||
case 4:
|
||||
modbusAddress.ReadFunction = 3;
|
||||
break;
|
||||
}
|
||||
modbusAddress.Address = (double.Parse(address.Substring(1)) - 1).ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringGeter = new();
|
||||
if (Station > 0)
|
||||
{
|
||||
stringGeter.Append("s=" + Station.ToString() + ";");
|
||||
stringGeter.Append($"s={Station.ToString()};");
|
||||
}
|
||||
if (WriteFunction > 0)
|
||||
{
|
||||
stringGeter.Append("w=" + WriteFunction.ToString() + ";");
|
||||
stringGeter.Append($"w={WriteFunction.ToString()};");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(SocketId))
|
||||
{
|
||||
stringGeter.Append($"id={SocketId};");
|
||||
}
|
||||
stringGeter.Append(GetFunctionString(ReadFunction) + (AddressStart + 1).ToString());
|
||||
return stringGeter.ToString();
|
||||
}
|
||||
|
||||
private void GetFunction(ushort readF)
|
||||
{
|
||||
switch (readF)
|
||||
{
|
||||
case 0:
|
||||
ReadFunction = 1;
|
||||
break;
|
||||
case 1:
|
||||
ReadFunction = 2;
|
||||
break;
|
||||
case 3:
|
||||
ReadFunction = 4;
|
||||
break;
|
||||
case 4:
|
||||
ReadFunction = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
private string GetFunctionString(int readF)
|
||||
{
|
||||
return readF switch
|
@@ -0,0 +1,334 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Bool;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
internal class ModbusHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加Crc16
|
||||
/// </summary>
|
||||
internal static byte[] AddCrc(byte[] command)
|
||||
{
|
||||
return EasyCRC16.CRC16(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加ModbusTcp报文头
|
||||
/// </summary>
|
||||
internal static byte[] AddModbusTcpHead(byte[] modbus, ushort id)
|
||||
{
|
||||
byte[] tcp = new byte[modbus.Length + 6];
|
||||
tcp[0] = BitConverter.GetBytes(id)[1];
|
||||
tcp[1] = BitConverter.GetBytes(id)[0];
|
||||
tcp[4] = BitConverter.GetBytes(modbus.Length)[1];
|
||||
tcp[5] = BitConverter.GetBytes(modbus.Length)[0];
|
||||
modbus.CopyTo(tcp, 6);
|
||||
return tcp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// modbus地址格式说明
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string GetAddressDescription()
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
stringBuilder.AppendLine("Modbus寄存器");
|
||||
stringBuilder.AppendLine("线圈寄存器使用从 00001 开始的地址编号。");
|
||||
stringBuilder.AppendLine("离散输入寄存器使用从 10001 开始的地址编号。");
|
||||
stringBuilder.AppendLine("输入寄存器使用从 30001 开始的地址编号。");
|
||||
stringBuilder.AppendLine("保持寄存器使用从 40001 开始的地址编号。");
|
||||
stringBuilder.AppendLine("举例:");
|
||||
stringBuilder.AppendLine("40001=>保持寄存器第一个寄存器");
|
||||
stringBuilder.AppendLine("额外格式:");
|
||||
stringBuilder.AppendLine("设备站号 ,比如40001;s=2; ,代表设备地址为2的保持寄存器第一个寄存器");
|
||||
stringBuilder.AppendLine("写入功能码 ,比如40001;w=16; ,代表保持寄存器第一个寄存器,写入值时采用0x10功能码,而不是默认的0x06功能码");
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 通过错误码来获取到对应的文本消息
|
||||
/// </summary>
|
||||
internal static string GetDescriptionByErrorCode(byte code)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
1 => "不支持的功能码",
|
||||
2 => "读取寄存器越界",
|
||||
3 => "读取长度超限",
|
||||
4 => "读写异常",
|
||||
_ => "未知错误",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取modbus数据区内容,返回数据需去除Crc和报文头,例如:01 03 02 00 01,发送数据需报文头
|
||||
/// </summary>
|
||||
/// <param name="send">发送数据</param>
|
||||
/// <param name="response">返回数据</param>
|
||||
/// <returns></returns>
|
||||
internal static OperResult<byte[], FilterResult> GetModbusData(byte[] send, byte[] response)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (response.Length < 3)
|
||||
return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
|
||||
|
||||
|
||||
if (response[1] >= 0x80)//错误码
|
||||
return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success };
|
||||
if (response[1] <= 0x05)
|
||||
{
|
||||
if ((response.Length < response[2] + 3))
|
||||
return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((response.Length < 6))
|
||||
return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
|
||||
|
||||
}
|
||||
|
||||
if (send.Length == 0)
|
||||
{
|
||||
var result = OperResult.CreateSuccessResult(GenericExtensions.ArrayRemoveBegin(response, 3), FilterResult.Success);
|
||||
result.Message = "接收数据正确,但主机并没有主动请求数据";
|
||||
return result;
|
||||
}
|
||||
if (send[0] != response[0])
|
||||
return new OperResult<byte[], FilterResult>(string.Format("站号不一致", send[0], response[0])) { Content2 = FilterResult.Success };
|
||||
if (send[1] != response[1])
|
||||
return new OperResult<byte[], FilterResult>() { Message = "功能码不一致", Content2 = FilterResult.Success };
|
||||
return OperResult.CreateSuccessResult(GenericExtensions.ArrayRemoveBegin(response, 3), FilterResult.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[], FilterResult>(ex) { Content2 = FilterResult.Success };
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 去除Crc,返回modbus数据区
|
||||
/// </summary>
|
||||
/// <param name="send"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="crcCheck"></param>
|
||||
/// <returns></returns>
|
||||
internal static OperResult<byte[], FilterResult> GetModbusRtuData(byte[] send, byte[] response, bool crcCheck = true)
|
||||
{
|
||||
if (response.Length < 3)
|
||||
return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
|
||||
|
||||
if (response[1] >= 0x80)//错误码
|
||||
return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success };
|
||||
if (response[1] <= 0x05)
|
||||
{
|
||||
if ((response.Length < response[2] + 5))
|
||||
return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((response.Length < 8))
|
||||
return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
|
||||
|
||||
}
|
||||
|
||||
|
||||
var data = response.SelectMiddle(0, response[2] != 0 ? response[2] + 5 : 8);
|
||||
if (crcCheck && !EasyCRC16.CheckCRC16(data))
|
||||
return new OperResult<byte[], FilterResult>("Crc校验失败" + DataTransUtil.ByteToHexString(data, ' ')) { Content2 = FilterResult.Success };
|
||||
return GetModbusData(send, data.RemoveLast(2));
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取读取报文
|
||||
/// </summary>
|
||||
internal static OperResult<byte[]> GetReadModbusCommand(string address, int length, byte station)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, station);
|
||||
return OperResult.CreateSuccessResult(GetReadModbusCommand(mAddress, length));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取写入布尔量报文,根据地址识别功能码
|
||||
/// </summary>
|
||||
internal static OperResult<byte[]> GetWriteBoolModbusCommand(string address, bool[] values, byte station)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, station);
|
||||
|
||||
//功能码或实际长度
|
||||
if (values?.Length > 1 || mAddress.WriteFunction == 15)
|
||||
return GetWriteBoolModbusCommand(mAddress, values, values.Length);
|
||||
else
|
||||
return GetWriteBoolModbusCommand(address, values[0], station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取写入字报文,根据地址识别功能码
|
||||
/// </summary>
|
||||
internal static OperResult<byte[]> GetWriteModbusCommand(string address, byte[] value, byte station)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, station);
|
||||
|
||||
//功能码或实际长度
|
||||
if (value?.Length > 2 || mAddress.WriteFunction == 16)
|
||||
return OperResult.CreateSuccessResult(GetWriteModbusCommand(mAddress, value));
|
||||
else
|
||||
return OperResult.CreateSuccessResult(GetWriteOneModbusCommand(mAddress, value));
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取读取报文
|
||||
/// </summary>
|
||||
internal static byte[] GetReadModbusCommand(ModbusAddress mAddress, int length)
|
||||
{
|
||||
byte[] array = new byte[6]
|
||||
{
|
||||
(byte) mAddress.Station,
|
||||
(byte) mAddress.ReadFunction,
|
||||
BitConverter.GetBytes(mAddress.AddressStart)[1],
|
||||
BitConverter.GetBytes(mAddress.AddressStart)[0],
|
||||
BitConverter.GetBytes(length)[1],
|
||||
BitConverter.GetBytes(length)[0]
|
||||
};
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取05写入布尔量报文
|
||||
/// </summary>
|
||||
internal static OperResult<byte[]> GetWriteBoolModbusCommand(string address, bool value, byte station)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (address.IndexOf('.') <= 0)
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, station);
|
||||
|
||||
return OperResult.CreateSuccessResult(GetWriteBoolModbusCommand(mAddress, value));
|
||||
}
|
||||
return new("不支持写入字寄存器的某一位");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取05写入布尔量报文
|
||||
/// </summary>
|
||||
private static byte[] GetWriteBoolModbusCommand(ModbusAddress mAddress, bool value)
|
||||
{
|
||||
byte[] array = new byte[6]
|
||||
{
|
||||
(byte) mAddress.Station,
|
||||
(byte)5,
|
||||
BitConverter.GetBytes(mAddress.AddressStart)[1],
|
||||
BitConverter.GetBytes(mAddress.AddressStart)[0],
|
||||
0,
|
||||
0
|
||||
};
|
||||
if (value)
|
||||
{
|
||||
array[4] = 0xFF;
|
||||
array[5] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
array[4] = 0;
|
||||
array[5] = 0;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取15写入布尔量报文
|
||||
/// </summary>
|
||||
internal static OperResult<byte[]> GetWriteBoolModbusCommand(ModbusAddress mAddress, bool[] values, int length)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] numArray1 = values.BoolArrayToByte();
|
||||
byte[] numArray2 = new byte[7 + numArray1.Length];
|
||||
numArray2[0] = (byte)mAddress.Station;
|
||||
numArray2[1] = (byte)15;
|
||||
numArray2[2] = BitConverter.GetBytes(mAddress.AddressStart)[1];
|
||||
numArray2[3] = BitConverter.GetBytes(mAddress.AddressStart)[0];
|
||||
numArray2[4] = (byte)(length / 256);
|
||||
numArray2[5] = (byte)(length % 256);
|
||||
numArray2[6] = (byte)numArray1.Length;
|
||||
numArray1.CopyTo(numArray2, 7);
|
||||
return OperResult.CreateSuccessResult(numArray2);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取16写入字报文
|
||||
/// </summary>
|
||||
internal static byte[] GetWriteModbusCommand(ModbusAddress mAddress, byte[] values)
|
||||
{
|
||||
byte[] numArray = new byte[7 + values.Length];
|
||||
numArray[0] = (byte)mAddress.Station;
|
||||
numArray[1] = (byte)16;
|
||||
numArray[2] = BitConverter.GetBytes(mAddress.AddressStart)[1];
|
||||
numArray[3] = BitConverter.GetBytes(mAddress.AddressStart)[0];
|
||||
numArray[4] = (byte)(values.Length / 2 / 256);
|
||||
numArray[5] = (byte)(values.Length / 2 % 256);
|
||||
numArray[6] = (byte)values.Length;
|
||||
values.CopyTo(numArray, 7);
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取6写入字报文
|
||||
/// </summary>
|
||||
internal static byte[] GetWriteOneModbusCommand(ModbusAddress mAddress, byte[] values)
|
||||
{
|
||||
byte[] numArray = new byte[4 + values.Length];
|
||||
numArray[0] = (byte)mAddress.Station;
|
||||
numArray[1] = (byte)6;
|
||||
numArray[2] = BitConverter.GetBytes(mAddress.AddressStart)[1];
|
||||
numArray[3] = BitConverter.GetBytes(mAddress.AddressStart)[0];
|
||||
values.CopyTo(numArray, 4);
|
||||
return numArray;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,191 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
/// <summary>
|
||||
/// ModbusRtu
|
||||
/// </summary>
|
||||
public class ModbusRtu : ReadWriteDevicesSerialSessionBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// ModbusRtu
|
||||
/// </summary>
|
||||
/// <param name="serialSession"></param>
|
||||
public ModbusRtu(SerialSession serialSession) : base(serialSession)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crc校验
|
||||
/// </summary>
|
||||
[Description("Crc校验")]
|
||||
public bool Crc16CheckEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
[Description("站号")]
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
ModbusRtuDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
Crc16CheckEnable = Crc16CheckEnable,
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
SerialSession.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
if (FrameTime != 0)
|
||||
Thread.Sleep(FrameTime);
|
||||
var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
await Task.Delay(FrameTime, cancellationToken);
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
@@ -41,46 +41,45 @@ public class ModbusRtuDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<M
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override OperResult<byte[], FilterResult> UnpackResponse(ModbusRtuMessage request, byte[] send, byte[] body, byte[] response)
|
||||
protected override FilterResult UnpackResponse(ModbusRtuMessage request, byte[] send, byte[] body, byte[] response)
|
||||
{
|
||||
//理想状态检测
|
||||
var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
|
||||
if (result.IsSuccess)
|
||||
//链路干扰时需剔除前缀中的多于字节,初步按站号+功能码找寻初始字节
|
||||
if (send?.Length > 0)
|
||||
{
|
||||
return OperResult.CreateSuccessResult(result.Content, FilterResult.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果返回错误,具体分析
|
||||
var op = result.Copy<byte[], FilterResult>();
|
||||
if (response.Length <= 1)
|
||||
int index = -1;
|
||||
for (int i = 0; i < response.Length - 1; i++)
|
||||
{
|
||||
//如果长度不足,返回缓存
|
||||
op.Content2 = FilterResult.Cache;
|
||||
return op;
|
||||
if (response[i] == send[0] && (response[i + 1] == send[1] || response[i + 1] == (send[1] + 0x80)))
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(response[1] <= 0x10))
|
||||
if (index >= 0)
|
||||
{
|
||||
//功能码不对,返回放弃
|
||||
op.Content2 = FilterResult.Success;
|
||||
return op;
|
||||
response = response.RemoveBegin(index);
|
||||
}
|
||||
|
||||
//理想状态检测
|
||||
var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
request.Content = result.Content;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((response.Length > response[2] + 4))
|
||||
{
|
||||
//如果长度已经超了,说明这段报文已经不能继续解析了,直接返回放弃
|
||||
op.Content2 = FilterResult.Success;
|
||||
return op;
|
||||
}
|
||||
else
|
||||
{
|
||||
//否则返回缓存
|
||||
op.Content2 = FilterResult.Cache;
|
||||
return op;
|
||||
}
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
}
|
||||
return result.Content2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -20,15 +20,12 @@ public class ModbusRtuMessage : MessageBase, IMessage
|
||||
public override int HeadBytesLength => -1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[] head)
|
||||
public override bool CheckHeadBytes(byte[] heads)
|
||||
{
|
||||
BodyLength = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SendBytesThen()
|
||||
{
|
||||
BodyLength = -1;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,185 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
/// <inheritdoc/>
|
||||
public class ModbusRtuOverTcp : ReadWriteDevicesTcpClientBase
|
||||
{
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ModbusRtuOverTcp(TcpClient tcpClient) : base(tcpClient)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crc校验
|
||||
/// </summary>
|
||||
[Description("Crc校验")]
|
||||
public bool Crc16CheckEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
[Description("站号")]
|
||||
public byte Station { get; set; } = 1;
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
ModbusRtuDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
Crc16CheckEnable = Crc16CheckEnable,
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
TcpClient.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
if (FrameTime != 0)
|
||||
Thread.Sleep(FrameTime);
|
||||
var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
await Task.Delay(FrameTime, cancellationToken);
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ModbusRtuOverUdp : ReadWriteDevicesUdpSessionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ModbusRtuOverUdp(UdpSession udpSession) : base(udpSession)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crc校验
|
||||
/// </summary>
|
||||
[Description("Crc校验")]
|
||||
public bool Crc16CheckEnable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
[Description("站号")]
|
||||
public byte Station { get; set; } = 1;
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
ModbusRtuOverUdpDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
Crc16CheckEnable = Crc16CheckEnable,
|
||||
};
|
||||
UdpSession.Config.SetUdpDataHandlingAdapter(() =>
|
||||
{
|
||||
return dataHandleAdapter;
|
||||
});
|
||||
UdpSession.Setup(UdpSession.Config);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
if (FrameTime != 0)
|
||||
Thread.Sleep(FrameTime);
|
||||
var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
await Task.Delay(FrameTime, cancellationToken);
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,9 +35,9 @@ public class ModbusRtuOverUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAd
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override OperResult<byte[]> UnpackResponse(
|
||||
byte[] send, byte[] response)
|
||||
protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response)
|
||||
{
|
||||
return ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
|
||||
var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,417 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Bool;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class ModbusSerialServer : ReadWriteDevicesSerialSessionBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
/// </summary>
|
||||
public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SerialSession, Task<OperResult>> WriteData;
|
||||
|
||||
/// <summary>
|
||||
/// 继电器
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 开关输入
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 输入寄存器
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 保持寄存器
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
|
||||
/// <inheritdoc/>
|
||||
public ModbusSerialServer(SerialSession serialSession) : base(serialSession)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多站点
|
||||
/// </summary>
|
||||
public bool MulStation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认站点
|
||||
/// </summary>
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (var item in ModbusServer01ByteBlocks)
|
||||
{
|
||||
item.Value.SafeDispose();
|
||||
}
|
||||
foreach (var item in ModbusServer02ByteBlocks)
|
||||
{
|
||||
item.Value.SafeDispose();
|
||||
}
|
||||
foreach (var item in ModbusServer03ByteBlocks)
|
||||
{
|
||||
item.Value.SafeDispose();
|
||||
}
|
||||
foreach (var item in ModbusServer04ByteBlocks)
|
||||
{
|
||||
item.Value.SafeDispose();
|
||||
}
|
||||
ModbusServer01ByteBlocks.Clear();
|
||||
ModbusServer02ByteBlocks.Clear();
|
||||
ModbusServer03ByteBlocks.Clear();
|
||||
ModbusServer04ByteBlocks.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ModbusAddress mAddress;
|
||||
try
|
||||
{
|
||||
mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
if (MulStation)
|
||||
{
|
||||
Init(mAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Station != mAddress.Station)
|
||||
{
|
||||
return new OperResult<byte[]>("地址错误");
|
||||
}
|
||||
Init(mAddress);
|
||||
|
||||
}
|
||||
|
||||
var ModbusServer01ByteBlock = ModbusServer01ByteBlocks[mAddress.Station];
|
||||
var ModbusServer02ByteBlock = ModbusServer02ByteBlocks[mAddress.Station];
|
||||
var ModbusServer03ByteBlock = ModbusServer03ByteBlocks[mAddress.Station];
|
||||
var ModbusServer04ByteBlock = ModbusServer04ByteBlocks[mAddress.Station];
|
||||
int len = mAddress.ReadFunction == 2 || mAddress.ReadFunction == 1 ? length : length * RegisterByteLength;
|
||||
switch (mAddress.ReadFunction)
|
||||
{
|
||||
case 1:
|
||||
byte[] bytes0 = new byte[len];
|
||||
ModbusServer01ByteBlock.Pos = mAddress.AddressStart;
|
||||
ModbusServer01ByteBlock.Read(bytes0);
|
||||
return OperResult.CreateSuccessResult(bytes0);
|
||||
case 2:
|
||||
byte[] bytes1 = new byte[len];
|
||||
ModbusServer02ByteBlock.Pos = mAddress.AddressStart;
|
||||
ModbusServer02ByteBlock.Read(bytes1);
|
||||
return OperResult.CreateSuccessResult(bytes1);
|
||||
case 3:
|
||||
|
||||
byte[] bytes3 = new byte[len];
|
||||
ModbusServer03ByteBlock.Pos = mAddress.AddressStart * RegisterByteLength;
|
||||
ModbusServer03ByteBlock.Read(bytes3);
|
||||
return OperResult.CreateSuccessResult(bytes3);
|
||||
case 4:
|
||||
byte[] bytes4 = new byte[len];
|
||||
ModbusServer04ByteBlock.Pos = mAddress.AddressStart * RegisterByteLength;
|
||||
ModbusServer04ByteBlock.Read(bytes4);
|
||||
return OperResult.CreateSuccessResult(bytes4);
|
||||
}
|
||||
return new OperResult<byte[]>("功能码错误");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Read(address, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient)
|
||||
{
|
||||
ModbusSerialServerDataHandleAdapter dataHandleAdapter = new();
|
||||
dataHandleAdapter.CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout);
|
||||
SerialSession.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ModbusAddress mAddress;
|
||||
try
|
||||
{
|
||||
mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
if (MulStation)
|
||||
{
|
||||
Init(mAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Station != mAddress.Station)
|
||||
{
|
||||
return new OperResult("地址错误");
|
||||
}
|
||||
Init(mAddress);
|
||||
}
|
||||
var ModbusServer03ByteBlock = ModbusServer03ByteBlocks[mAddress.Station];
|
||||
var ModbusServer04ByteBlock = ModbusServer04ByteBlocks[mAddress.Station];
|
||||
switch (mAddress.ReadFunction)
|
||||
{
|
||||
case 3:
|
||||
ModbusServer03ByteBlock.Pos = mAddress.AddressStart * RegisterByteLength;
|
||||
ModbusServer03ByteBlock.Write(value);
|
||||
return OperResult.CreateSuccessResult();
|
||||
case 4:
|
||||
ModbusServer04ByteBlock.Pos = mAddress.AddressStart * RegisterByteLength;
|
||||
ModbusServer04ByteBlock.Write(value);
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
return new OperResult("功能码错误");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ModbusAddress mAddress;
|
||||
try
|
||||
{
|
||||
mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (new OperResult(ex));
|
||||
}
|
||||
if (MulStation)
|
||||
{
|
||||
Init(mAddress);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Station != mAddress.Station)
|
||||
{
|
||||
return (new OperResult("地址错误"));
|
||||
}
|
||||
Init(mAddress);
|
||||
|
||||
}
|
||||
|
||||
var ModbusServer01ByteBlock = ModbusServer01ByteBlocks[mAddress.Station];
|
||||
var ModbusServer02ByteBlock = ModbusServer02ByteBlocks[mAddress.Station];
|
||||
switch (mAddress.ReadFunction)
|
||||
{
|
||||
case 1:
|
||||
ModbusServer01ByteBlock.Pos = mAddress.AddressStart;
|
||||
ModbusServer01ByteBlock.Write(value.BoolArrayToByte());
|
||||
return (OperResult.CreateSuccessResult());
|
||||
case 2:
|
||||
ModbusServer02ByteBlock.Pos = mAddress.AddressStart;
|
||||
ModbusServer02ByteBlock.Write(value.BoolArrayToByte());
|
||||
return (OperResult.CreateSuccessResult());
|
||||
}
|
||||
return new OperResult("功能码错误");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Write(address, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Write(address, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task Received(SerialSession client, ReceivedDataEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestInfo = e.RequestInfo;
|
||||
//接收外部报文
|
||||
if (requestInfo is ModbusSerialServerMessage modbusServerMessage)
|
||||
{
|
||||
if (modbusServerMessage.CurModbusAddress == null)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
}
|
||||
if (!modbusServerMessage.IsSuccess)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
}
|
||||
|
||||
if (modbusServerMessage.CurModbusAddress.WriteFunction == 0)//读取
|
||||
{
|
||||
var data = Read(modbusServerMessage.CurModbusAddress.ToString(), modbusServerMessage.Length);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
var coreData = data.Content;
|
||||
if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
coreData = data.Content.Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling(modbusServerMessage.Length / 8.0));
|
||||
}
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 2).SpliceArray(new byte[] { (byte)coreData.Length }, coreData);
|
||||
SerialSession.Send(sendData);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);//返回错误码
|
||||
}
|
||||
}
|
||||
else//写入
|
||||
{
|
||||
var coreData = modbusServerMessage.Content;
|
||||
if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
//写入继电器
|
||||
if (WriteData != null)
|
||||
{
|
||||
// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
if ((await WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, SerialSession)).IsSuccess)
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.Length));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(SerialSession, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入内存区
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.Length));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(SerialSession, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入寄存器
|
||||
if (WriteData != null)
|
||||
{
|
||||
|
||||
if ((await WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, SerialSession)).IsSuccess)
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(SerialSession, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(SerialSession, modbusServerMessage);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(SerialSession, modbusServerMessage);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, ToString());
|
||||
}
|
||||
//返回错误码
|
||||
static void WriteError(SerialSession SerialSession, ModbusSerialServerMessage modbusServerMessage)
|
||||
{
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 2)
|
||||
.SpliceArray(new byte[] { (byte)1 });//01 lllegal function
|
||||
sendData[1] = (byte)(sendData[1] + 128);
|
||||
SerialSession.Send(sendData);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteSuccess03(SerialSession SerialSession, ModbusSerialServerMessage modbusServerMessage)
|
||||
{
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 6);
|
||||
SerialSession.Send(sendData);
|
||||
}
|
||||
|
||||
private void Init(ModbusAddress mAddress)
|
||||
{
|
||||
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
}
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ModbusSerialServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusSerialServerMessage>
|
||||
{
|
||||
private readonly ThingsGatewayBitConverter ThingsGatewayBitConverter = new(EndianType.Big);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
public override byte[] PackCommand(byte[] command)
|
||||
{
|
||||
return ModbusHelper.AddCrc(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取modbus写入数据区内容
|
||||
/// </summary>
|
||||
/// <param name="response">返回数据</param>
|
||||
/// <returns></returns>
|
||||
internal OperResult<byte[]> GetModbusData(byte[] response)
|
||||
{
|
||||
try
|
||||
{
|
||||
var func = ThingsGatewayBitConverter.ToByte(response, 1);
|
||||
if (func == 1 || func == 2 || func == 3 || func == 4 || func == 5 || func == 6)
|
||||
{
|
||||
if (response.Length == 6)
|
||||
return OperResult.CreateSuccessResult(response);
|
||||
}
|
||||
else if (func == 15 || func == 16)
|
||||
{
|
||||
var length = ThingsGatewayBitConverter.ToByte(response, 6);
|
||||
if (response.Length == 7 + length)
|
||||
{
|
||||
return OperResult.CreateSuccessResult(response);
|
||||
}
|
||||
}
|
||||
|
||||
return new OperResult<byte[]>() { Message = $"数据长度{response.Length}错误" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ModbusSerialServerMessage GetInstance()
|
||||
{
|
||||
return new ModbusSerialServerMessage();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override FilterResult UnpackResponse(ModbusSerialServerMessage request, byte[] send, byte[] body, byte[] response)
|
||||
{
|
||||
var result1 = ModbusHelper.GetModbusRtuData(new byte[0], response, true);
|
||||
if (result1.IsSuccess)
|
||||
{
|
||||
var result = GetModbusData(response.RemoveLast(2));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
//解析01 03 00 00 00 0A
|
||||
var station = ThingsGatewayBitConverter.ToByte(response, 0);
|
||||
var function = ThingsGatewayBitConverter.ToByte(response, 1);
|
||||
int addressStart = ThingsGatewayBitConverter.ToInt16(response, 2);
|
||||
if (addressStart == -1)
|
||||
{
|
||||
addressStart = 65535;
|
||||
}
|
||||
if (function > 4)
|
||||
{
|
||||
if (function > 6)
|
||||
{
|
||||
request.CurModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
Address = addressStart.ToString(),
|
||||
WriteFunction = function,
|
||||
ReadFunction = (byte)(function == 16 ? 3 : function == 15 ? 1 : 3),
|
||||
};
|
||||
request.Length = ThingsGatewayBitConverter.ToByte(response, 5);
|
||||
request.Content = result.Content.RemoveBegin(7);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.CurModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
Address = addressStart.ToString(),
|
||||
WriteFunction = function,
|
||||
ReadFunction = (byte)(function == 6 ? 3 : function == 5 ? 1 : 3),
|
||||
};
|
||||
request.Length = 1;
|
||||
request.Content = result.Content.RemoveBegin(4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
request.CurModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
Address = addressStart.ToString(),
|
||||
ReadFunction = function,
|
||||
};
|
||||
request.Length = ThingsGatewayBitConverter.ToByte(response, 5);
|
||||
}
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
return FilterResult.Cache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return result1.Content2;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class ModbusSerialServerMessage : MessageBase, IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前关联的地址
|
||||
/// </summary>
|
||||
public ModbusAddress CurModbusAddress { get; set; }
|
||||
/// <summary>
|
||||
/// 当前读写的数据长度
|
||||
/// </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => -1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[] heads)
|
||||
{
|
||||
BodyLength = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
/// <inheritdoc/>
|
||||
public class ModbusTcp : ReadWriteDevicesTcpClientBase
|
||||
{
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ModbusTcp(TcpClient tcpClient) : base(tcpClient)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
/// <summary>
|
||||
/// 检测事务标识符
|
||||
/// </summary>
|
||||
[Description("检测事务标识符")]
|
||||
public bool IsCheckMessageId { get; set; }
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
[Description("站号")]
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
var data = SendThenReturn(commandResult, cancellationToken);
|
||||
return data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
var data = await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
return data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
ModbusTcpDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
IsCheckMessageId = IsCheckMessageId,
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
TcpClient.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
if (FrameTime != 0)
|
||||
Thread.Sleep(FrameTime);
|
||||
var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
await Task.Delay(FrameTime, cancellationToken);
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,13 +10,12 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// ModbusTcpDataHandleAdapter
|
||||
/// </summary>
|
||||
public class ModbusTcpDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusTcpMessage>
|
||||
{
|
||||
@@ -50,37 +49,22 @@ public class ModbusTcpDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<M
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override OperResult<byte[], FilterResult> UnpackResponse(ModbusTcpMessage request, byte[] send, byte[] body, byte[] response)
|
||||
protected override FilterResult UnpackResponse(ModbusTcpMessage request, byte[] send, byte[] body, byte[] response)
|
||||
{
|
||||
//理想状态检测
|
||||
var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return OperResult.CreateSuccessResult(result.Content, FilterResult.Success);
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
request.Content = result.Content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果返回错误,具体分析
|
||||
var op = result.Copy<byte[], FilterResult>();
|
||||
if (response.Length < 10)
|
||||
{
|
||||
//如果长度不足,返回缓存
|
||||
op.Content2 = FilterResult.Cache;
|
||||
return op;
|
||||
}
|
||||
if ((response.Length > response[8] + 9))
|
||||
{
|
||||
//如果长度已经超了,说明这段报文已经不能继续解析了,直接返回放弃
|
||||
op.Content2 = FilterResult.Success;
|
||||
return op;
|
||||
}
|
||||
else
|
||||
{
|
||||
//否则返回缓存
|
||||
op.Content2 = FilterResult.Cache;
|
||||
return op;
|
||||
}
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
}
|
||||
return result.Content2;
|
||||
|
||||
}
|
||||
}
|
@@ -23,10 +23,10 @@ public class ModbusTcpMessage : MessageBase, IMessage
|
||||
/// </summary>
|
||||
public bool IsCheckMessageId { get; set; } = false;
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[] head)
|
||||
public override bool CheckHeadBytes(byte[] heads)
|
||||
{
|
||||
if (head == null || head.Length <= 0) return false;
|
||||
HeadBytes = head;
|
||||
if (heads == null || heads.Length <= 0) return false;
|
||||
HeadBytes = heads;
|
||||
|
||||
int num = (HeadBytes[4] * 256) + HeadBytes[5];
|
||||
BodyLength = num;
|
@@ -0,0 +1,249 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
/// <inheritdoc/>
|
||||
public class ModbusTcpDtu : ReadWriteDevicesTcpServerBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ModbusTcpDtu(TcpService tcpService) : base(tcpService)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
ModbusTcpDtuPlugin modbusTcpSalvePlugin = new ModbusTcpDtuPlugin();
|
||||
tcpService.Config.ConfigurePlugins(a =>
|
||||
{
|
||||
a.Add(modbusTcpSalvePlugin);
|
||||
});
|
||||
tcpService.Setup(tcpService.Config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测事务标识符
|
||||
/// </summary>
|
||||
[Description("检测事务标识符")]
|
||||
public bool IsCheckMessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
[Description("站号")]
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return SendThenReturn(mAddress.SocketId, commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return await SendThenReturnAsync(mAddress.SocketId, commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
if (socketClient is SocketClient client)
|
||||
{
|
||||
ModbusTcpDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
IsCheckMessageId = IsCheckMessageId,
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
client.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in TcpService.GetClients())
|
||||
{
|
||||
ModbusTcpDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
IsCheckMessageId = IsCheckMessageId,
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
item.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return SendThenReturn(mAddress.SocketId, commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return SendThenReturn(mAddress.SocketId, commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(mAddress.SocketId, commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(mAddress.SocketId, commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult<byte[]> SendThenReturn(string id, OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
if (TcpService.TryGetSocketClient($"ID={id}", out var client))
|
||||
{
|
||||
SetDataAdapter(client);
|
||||
|
||||
var item = commandResult.Content;
|
||||
if (FrameTime != 0)
|
||||
Thread.Sleep(FrameTime);
|
||||
var WaitingClientEx = client.CreateWaitingClient(new() { ThrowBreakException = true });
|
||||
var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>("客户端未连接");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task<OperResult<byte[]>> SendThenReturnAsync(string id, OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
if (TcpService.TryGetSocketClient($"ID={id}", out var client))
|
||||
{
|
||||
SetDataAdapter(client);
|
||||
|
||||
var item = commandResult.Content;
|
||||
await Task.Delay(FrameTime, cancellationToken);
|
||||
var WaitingClientEx = client.CreateWaitingClient(new() { ThrowBreakException = true });
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>("客户端未连接");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal class ModbusTcpDtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
public Task OnTcpReceiving(ITcpClientBase client, ByteBlockEventArgs e)
|
||||
{
|
||||
if (client is ISocketClient socket)
|
||||
{
|
||||
if (!socket.Id.StartsWith("ID="))
|
||||
{
|
||||
ByteBlock byteBlock = e.ByteBlock;
|
||||
var id = $"ID={byteBlock.ToArray().ToHexString()}";
|
||||
socket.ResetId(id);
|
||||
}
|
||||
}
|
||||
return e.InvokeNext();//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,43 +11,43 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Bool;
|
||||
using ThingsGateway.Foundation.Extension.Byte;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class ModbusServer : ReadWriteDevicesTcpServerBase
|
||||
public class ModbusTcpServer : ReadWriteDevicesTcpServerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
/// </summary>
|
||||
public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SocketClient, Task<OperResult>> WriteData;
|
||||
|
||||
/// <summary>
|
||||
/// 继电器
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 开关输入
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 输入寄存器
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 保持寄存器
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
|
||||
/// <summary>
|
||||
/// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
/// </summary>
|
||||
public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SocketClient, OperResult> WriteData;
|
||||
|
||||
private ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
|
||||
/// <inheritdoc/>
|
||||
public ModbusServer(TcpService tcpService) : base(tcpService)
|
||||
public ModbusTcpServer(TcpService tcpService) : base(tcpService)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
@@ -56,46 +56,18 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
|
||||
/// <summary>
|
||||
/// 多站点
|
||||
/// </summary>
|
||||
[Description("多站点")]
|
||||
public bool MulStation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认站点
|
||||
/// </summary>
|
||||
[Description("默认站点")]
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Read(address, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(SocketClient client)
|
||||
{
|
||||
ModbusServerDataHandleAdapter DataHandleAdapter = new();
|
||||
client.SetDataHandlingAdapter(DataHandleAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Write(address, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Write(address, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (var item in ModbusServer01ByteBlocks)
|
||||
{
|
||||
@@ -117,167 +89,28 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
|
||||
ModbusServer02ByteBlocks.Clear();
|
||||
ModbusServer03ByteBlocks.Clear();
|
||||
ModbusServer04ByteBlocks.Clear();
|
||||
Disconnect();
|
||||
base.Dispose(disposing);
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Received(SocketClient client, IRequestInfo requestInfo)
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
//接收外部报文
|
||||
if (requestInfo is ModbusServerMessage modbusServerMessage)
|
||||
{
|
||||
if (!modbusServerMessage.IsSuccess)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
}
|
||||
if (modbusServerMessage.CurModbusAddress == null)
|
||||
{
|
||||
WriteError(client, modbusServerMessage);//无法解析变量地址,返回错误码
|
||||
}
|
||||
if (modbusServerMessage.CurModbusAddress.WriteFunction == 0)//读取
|
||||
{
|
||||
var data = Read(modbusServerMessage.CurModbusAddress.ToString(), modbusServerMessage.CurModbusAddress.Length);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
var coreData = data.Content;
|
||||
if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
coreData = data.Content.Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling((double)modbusServerMessage.CurModbusAddress.Length / 8.0));
|
||||
}
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8)
|
||||
.SpliceArray(new byte[] { (byte)coreData.Length }, coreData);
|
||||
sendData[5] = (byte)(sendData.Length - 6);
|
||||
client.Send(sendData);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);//返回错误码
|
||||
}
|
||||
}
|
||||
else//写入
|
||||
{
|
||||
var coreData = modbusServerMessage.Content;
|
||||
if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
//写入继电器
|
||||
if (WriteData != null)
|
||||
{
|
||||
// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
if ((WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.CurModbusAddress.Length));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入内存区
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.CurModbusAddress.Length));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入寄存器
|
||||
if (WriteData != null)
|
||||
{
|
||||
|
||||
if ((WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//返回错误码
|
||||
static void WriteError(SocketClient client, ModbusServerMessage modbusServerMessage)
|
||||
{
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8)
|
||||
.SpliceArray(new byte[] { (byte)1 });//01 lllegal function
|
||||
sendData[5] = (byte)(sendData.Length - 6);
|
||||
sendData[7] = (byte)(sendData[7] + 128);
|
||||
client.Send(sendData);
|
||||
|
||||
|
||||
}
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
|
||||
private static void WriteSuccess03(SocketClient client, ModbusServerMessage modbusServerMessage)
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 12);
|
||||
sendData[5] = (byte)(sendData.Length - 6);
|
||||
client.Send(sendData);
|
||||
}
|
||||
|
||||
private void Init(ModbusAddress mAddress)
|
||||
{
|
||||
if (ModbusServer01ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
|
||||
ModbusServer01ByteBlocks[mAddress.Station].SetLength(1024 * 128);
|
||||
if (ModbusServer02ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
|
||||
ModbusServer02ByteBlocks[mAddress.Station].SetLength(1024 * 128);
|
||||
if (ModbusServer03ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
|
||||
ModbusServer03ByteBlocks[mAddress.Station].SetLength(1024 * 128);
|
||||
if (ModbusServer04ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
|
||||
ModbusServer04ByteBlocks[mAddress.Station].SetLength(1024 * 128);
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
|
||||
}
|
||||
|
||||
private OperResult<byte[]> Read(string address, int length)
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ModbusAddress mAddress;
|
||||
try
|
||||
{
|
||||
mAddress = new ModbusAddress(address, Station);
|
||||
mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -328,12 +161,42 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
|
||||
}
|
||||
return new OperResult<byte[]>("功能码错误");
|
||||
}
|
||||
private OperResult Write(string address, byte[] value)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Read(address, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient)
|
||||
{
|
||||
if (socketClient is SocketClient client)
|
||||
{
|
||||
ModbusTcpServerDataHandleAdapter dataHandleAdapter = new();
|
||||
dataHandleAdapter.CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout);
|
||||
client.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in TcpService.GetClients())
|
||||
{
|
||||
ModbusTcpDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
item.SetDataHandlingAdapter(dataHandleAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ModbusAddress mAddress;
|
||||
try
|
||||
{
|
||||
mAddress = new ModbusAddress(address, Station);
|
||||
mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -366,12 +229,14 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
|
||||
}
|
||||
return new OperResult("功能码错误");
|
||||
}
|
||||
private OperResult Write(string address, bool[] value)
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ModbusAddress mAddress;
|
||||
try
|
||||
{
|
||||
mAddress = new ModbusAddress(address, Station);
|
||||
mAddress = ModbusAddress.ParseFrom(address, Station);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -407,4 +272,166 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
|
||||
}
|
||||
return new OperResult("功能码错误");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Write(address, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Write(address, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task Received(SocketClient client, ReceivedDataEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestInfo = e.RequestInfo;
|
||||
//接收外部报文
|
||||
if (requestInfo is ModbusTcpServerMessage modbusServerMessage)
|
||||
{
|
||||
if (modbusServerMessage.CurModbusAddress == null)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
}
|
||||
if (!modbusServerMessage.IsSuccess)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
}
|
||||
|
||||
if (modbusServerMessage.CurModbusAddress.WriteFunction == 0)//读取
|
||||
{
|
||||
var data = Read(modbusServerMessage.CurModbusAddress.ToString(), modbusServerMessage.Length);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
var coreData = data.Content;
|
||||
if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
coreData = data.Content.Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling(modbusServerMessage.Length / 8.0));
|
||||
}
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8).SpliceArray(new byte[] { (byte)coreData.Length }, coreData);
|
||||
sendData[5] = (byte)(sendData.Length - 6);
|
||||
client.Send(sendData);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);//返回错误码
|
||||
}
|
||||
}
|
||||
else//写入
|
||||
{
|
||||
var coreData = modbusServerMessage.Content;
|
||||
if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
//写入继电器
|
||||
if (WriteData != null)
|
||||
{
|
||||
// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
if ((await WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.Length));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入内存区
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.Length));
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入寄存器
|
||||
if (WriteData != null)
|
||||
{
|
||||
|
||||
if ((await WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
WriteSuccess03(client, modbusServerMessage);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(client, modbusServerMessage);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, ToString());
|
||||
}
|
||||
//返回错误码
|
||||
static void WriteError(SocketClient client, ModbusTcpServerMessage modbusServerMessage)
|
||||
{
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8)
|
||||
.SpliceArray(new byte[] { (byte)1 });//01 lllegal function
|
||||
sendData[5] = (byte)(sendData.Length - 6);
|
||||
sendData[7] = (byte)(sendData[7] + 128);
|
||||
client.Send(sendData);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteSuccess03(SocketClient client, ModbusTcpServerMessage modbusServerMessage)
|
||||
{
|
||||
var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 12);
|
||||
sendData[5] = (byte)(sendData.Length - 6);
|
||||
client.Send(sendData);
|
||||
}
|
||||
|
||||
private void Init(ModbusAddress mAddress)
|
||||
{
|
||||
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a => new ByteBlock(1024 * 128));
|
||||
}
|
||||
}
|
@@ -10,13 +10,12 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusServerMessage>
|
||||
public class ModbusTcpServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusTcpServerMessage>
|
||||
{
|
||||
private readonly ThingsGatewayBitConverter ThingsGatewayBitConverter = new(EndianType.Big);
|
||||
|
||||
@@ -54,22 +53,22 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
|
||||
}
|
||||
}
|
||||
|
||||
return new OperResult<byte[]>(response) { Message = $"数据长度{response.Length}错误" };
|
||||
return new OperResult<byte[]>() { Message = $"数据长度{response.Length}错误" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex.Message);
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ModbusServerMessage GetInstance()
|
||||
protected override ModbusTcpServerMessage GetInstance()
|
||||
{
|
||||
return new ModbusServerMessage();
|
||||
return new ModbusTcpServerMessage();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override OperResult<byte[], FilterResult> UnpackResponse(ModbusServerMessage request, byte[] send, byte[] body, byte[] response)
|
||||
protected override FilterResult UnpackResponse(ModbusTcpServerMessage request, byte[] send, byte[] body, byte[] response)
|
||||
{
|
||||
var result = GetModbusData(response.RemoveBegin(6));
|
||||
if (result.IsSuccess)
|
||||
@@ -89,11 +88,11 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
|
||||
request.CurModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
AddressStart = addressStart,
|
||||
Address = addressStart.ToString(),
|
||||
WriteFunction = function,
|
||||
ReadFunction = (byte)(function == 16 ? 3 : function == 15 ? 1 : 3),
|
||||
Length = ThingsGatewayBitConverter.ToByte(response, 11),
|
||||
};
|
||||
request.Length = ThingsGatewayBitConverter.ToByte(response, 11);
|
||||
request.Content = result.Content.RemoveBegin(7);
|
||||
}
|
||||
else
|
||||
@@ -101,11 +100,11 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
|
||||
request.CurModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
AddressStart = addressStart,
|
||||
Address = addressStart.ToString(),
|
||||
WriteFunction = function,
|
||||
ReadFunction = (byte)(function == 6 ? 3 : function == 5 ? 1 : 3),
|
||||
Length = 1,
|
||||
};
|
||||
request.Length = 1;
|
||||
request.Content = result.Content.RemoveBegin(4);
|
||||
}
|
||||
}
|
||||
@@ -114,19 +113,20 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
|
||||
request.CurModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
AddressStart = addressStart,
|
||||
Address = addressStart.ToString(),
|
||||
ReadFunction = function,
|
||||
Length = ThingsGatewayBitConverter.ToByte(response, 11),
|
||||
};
|
||||
request.Length = ThingsGatewayBitConverter.ToByte(response, 11);
|
||||
}
|
||||
|
||||
return OperResult.CreateSuccessResult(request.Content, FilterResult.Success);
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
var op = result.Copy<byte[], FilterResult>();
|
||||
op.Content2 = FilterResult.Cache;
|
||||
return op;
|
||||
request.ErrorCode = result.ErrorCode;
|
||||
request.Message = result.Message;
|
||||
return FilterResult.Cache;
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,20 +15,24 @@ namespace ThingsGateway.Foundation.Adapter.Modbus
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class ModbusServerMessage : MessageBase, IMessage
|
||||
public class ModbusTcpServerMessage : MessageBase, IMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前关联的地址
|
||||
/// </summary>
|
||||
public ModbusAddress CurModbusAddress { get; set; }
|
||||
/// <summary>
|
||||
/// 当前读写的数据长度
|
||||
/// </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 6;
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[] head)
|
||||
public override bool CheckHeadBytes(byte[] heads)
|
||||
{
|
||||
if (head == null || head.Length != 6) return false;
|
||||
HeadBytes = head;
|
||||
if (heads == null || heads.Length != 6) return false;
|
||||
HeadBytes = heads;
|
||||
|
||||
int num = (HeadBytes[4] * 256) + HeadBytes[5];
|
||||
BodyLength = num;
|
@@ -0,0 +1,187 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ModbusUdp : ReadWriteDevicesUdpSessionBase
|
||||
{
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ModbusUdp(UdpSession udpSession) : base(udpSession)
|
||||
{
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
RegisterByteLength = 2;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 检测事务标识符
|
||||
/// </summary>
|
||||
[Description("检测事务标识符")]
|
||||
public bool IsCheckMessageId { get; set; }
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
[Description("站号")]
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
|
||||
{
|
||||
return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetDataAdapter(object socketClient = null)
|
||||
{
|
||||
ModbusUdpDataHandleAdapter dataHandleAdapter = new()
|
||||
{
|
||||
IsCheckMessageId = IsCheckMessageId
|
||||
};
|
||||
UdpSession.Config.SetUdpDataHandlingAdapter(() =>
|
||||
{
|
||||
return dataHandleAdapter;
|
||||
});
|
||||
UdpSession.Setup(UdpSession.Config);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Connect(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return SendThenReturn(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(cancellationToken);
|
||||
var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
|
||||
return await SendThenReturnAsync(commandResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
if (FrameTime != 0)
|
||||
Thread.Sleep(FrameTime);
|
||||
var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
|
||||
{
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var item = commandResult.Content;
|
||||
await Task.Delay(FrameTime, cancellationToken);
|
||||
var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
|
||||
return (MessageBase)result.RequestInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>(commandResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -50,6 +50,7 @@ public class ModbusUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<M
|
||||
/// <inheritdoc/>
|
||||
protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response)
|
||||
{
|
||||
return ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6));
|
||||
var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6));
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -10,131 +10,138 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using ThingsGateway.Application;
|
||||
using ThingsGateway.Application.Extensions;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.Adapter.Modbus;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Modbus;
|
||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
|
||||
|
||||
internal static class ModbusHelper
|
||||
/// <summary>
|
||||
/// PackHelper
|
||||
/// </summary>
|
||||
public class PackHelper
|
||||
{
|
||||
internal static List<DeviceVariableSourceRead> LoadSourceRead(this List<DeviceVariableRunTime> deviceVariables, IReadWriteDevice device, int MaxPack)
|
||||
/// <summary>
|
||||
/// 打包变量,添加到<see href="deviceVariableSourceReads"></see>
|
||||
/// </summary>
|
||||
/// <param name="device"></param>
|
||||
/// <param name="deviceVariables"></param>
|
||||
/// <param name="maxPack">最大打包长度</param>
|
||||
/// <returns></returns>
|
||||
public static List<T> LoadSourceRead<T, T2>(IReadWrite device, List<T2> deviceVariables, int maxPack) where T : IDeviceVariableSourceRead<IDeviceVariableRunTime>, new() where T2 : IDeviceVariableRunTime, new()
|
||||
{
|
||||
if (deviceVariables == null)
|
||||
throw new ArgumentNullException(nameof(deviceVariables));
|
||||
|
||||
var deviceVariableSourceReads = new List<T>();
|
||||
var byteConverter = device.ThingsGatewayBitConverter;
|
||||
var result = new List<DeviceVariableSourceRead>();
|
||||
//需要先剔除额外信息,比如dataformat等
|
||||
foreach (var item in deviceVariables)
|
||||
{
|
||||
var address = item.VariableAddress;
|
||||
|
||||
IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, byteConverter);
|
||||
item.ThingsGatewayBitConverter = transformParameter;
|
||||
//item.VariableAddress = address;
|
||||
item.Index = device.GetBitOffset(item.VariableAddress);
|
||||
}
|
||||
|
||||
//按读取间隔分组
|
||||
var tags = deviceVariables.GroupBy(it => it.IntervalTime);
|
||||
foreach (var item in tags)
|
||||
var deviceVariableRunTimeGroups = deviceVariables.GroupBy(it => it.IntervalTime);
|
||||
foreach (var group in deviceVariableRunTimeGroups)
|
||||
{
|
||||
Dictionary<ModbusAddress, DeviceVariableRunTime> map = item.ToDictionary(it =>
|
||||
Dictionary<ModbusAddress, T2> map = group.ToDictionary(it =>
|
||||
{
|
||||
var lastLen = it.DataTypeEnum.GetByteLength();
|
||||
if (lastLen <= 0)
|
||||
{
|
||||
if (it.DataTypeEnum.GetSystemType() == typeof(bool))
|
||||
switch (it.DataTypeEnum)
|
||||
{
|
||||
lastLen = 2;
|
||||
}
|
||||
else if (it.DataTypeEnum.GetSystemType() == typeof(string))
|
||||
{
|
||||
lastLen = it.ThingsGatewayBitConverter.StringLength;
|
||||
}
|
||||
else if (it.DataTypeEnum.GetSystemType() == typeof(object))
|
||||
{
|
||||
lastLen = 2;
|
||||
case DataTypeEnum.String:
|
||||
lastLen = it.ThingsGatewayBitConverter.Length == null ? throw new("数据类型为字符串时,必须指定字符串长度,才能进行打包") : it.ThingsGatewayBitConverter.Length.Value;
|
||||
break;
|
||||
default:
|
||||
lastLen = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (it.ThingsGatewayBitConverter.Length != null && it.DataTypeEnum != DataTypeEnum.String)
|
||||
{
|
||||
lastLen *= it.ThingsGatewayBitConverter.Length.Value;
|
||||
}
|
||||
|
||||
var address = it.VariableAddress;
|
||||
if (address.IndexOf('.') > 0)
|
||||
{
|
||||
var addressSplits = address.SplitDot();
|
||||
|
||||
address = addressSplits.RemoveLast(1).ArrayToString(".");
|
||||
}
|
||||
|
||||
var result = new ModbusAddress(address, (ushort)lastLen);
|
||||
if (result == null)
|
||||
{
|
||||
}
|
||||
|
||||
var result = ModbusAddress.ParseFrom(address);
|
||||
result.ByteLength = lastLen;
|
||||
return result;
|
||||
});
|
||||
|
||||
//获取变量的地址
|
||||
var modbusAddressList = map.Keys.ToList();
|
||||
var modbusAddressList = map.Keys;
|
||||
|
||||
//获取功能码
|
||||
var functionCodes = modbusAddressList.Select(t => t.ReadFunction).Distinct();
|
||||
foreach (var functionCode in functionCodes)
|
||||
{
|
||||
var modbusAddressSameFunList = modbusAddressList
|
||||
.Where(t => t.ReadFunction == functionCode).ToList();
|
||||
var stationNumbers = modbusAddressSameFunList
|
||||
.Select(t => t.Station).Distinct().ToList();
|
||||
var modbusAddressSameFunList = modbusAddressList.Where(t => t.ReadFunction == functionCode);
|
||||
var stationNumbers = modbusAddressSameFunList.Select(t => t.Station).Distinct();
|
||||
foreach (var stationNumber in stationNumbers)
|
||||
{
|
||||
var addressList = modbusAddressSameFunList.Where(t => t.Station == stationNumber)
|
||||
var addressList = modbusAddressSameFunList
|
||||
.Where(t => t.Station == stationNumber)
|
||||
.ToDictionary(t => t, t => map[t]);
|
||||
var tempResult = LoadSourceRead(addressList, functionCode, item.Key, MaxPack);
|
||||
result.AddRange(tempResult);
|
||||
|
||||
var tempResult = LoadSourceRead<T, T2>(addressList, functionCode, group.Key, maxPack);
|
||||
deviceVariableSourceReads.AddRange(tempResult);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
|
||||
return deviceVariableSourceReads;
|
||||
}
|
||||
|
||||
private static List<DeviceVariableSourceRead> LoadSourceRead(Dictionary<ModbusAddress, DeviceVariableRunTime> addressList, int functionCode, int timeInterval, int MaxPack)
|
||||
private static List<T> LoadSourceRead<T, T2>(Dictionary<ModbusAddress, T2> addressList, int functionCode, int intervalTime, int maxPack) where T : IDeviceVariableSourceRead<IDeviceVariableRunTime>, new() where T2 : IDeviceVariableRunTime, new()
|
||||
{
|
||||
List<DeviceVariableSourceRead> sourceReads = new();
|
||||
List<T> sourceReads = new();
|
||||
//按地址和长度排序
|
||||
var orderByAddressAndLen = addressList.Keys.OrderBy(it => it.AddressStart + Math.Ceiling(it.Length / 2.0)).ToList();
|
||||
var orderByAddressEnd = addressList.Keys.OrderBy(it => it.AddressEnd);
|
||||
//按地址和长度排序
|
||||
var orderByAddress = addressList.Keys.OrderBy(it => it.AddressStart).ToList();
|
||||
var orderByAddressStart = addressList.Keys.OrderBy(it => it.AddressStart);
|
||||
//地址最小,在循环中更改
|
||||
var minAddress = orderByAddress.First().AddressStart;
|
||||
var minAddress = orderByAddressStart.First().AddressStart;
|
||||
//地址最大
|
||||
var maxAddress = orderByAddress.Last().AddressStart;
|
||||
var maxAddress = orderByAddressStart.Last().AddressStart;
|
||||
|
||||
while (maxAddress >= minAddress)
|
||||
{
|
||||
//最大的打包长度
|
||||
int readLength = MaxPack;
|
||||
int readLength = maxPack;
|
||||
if (functionCode == 1 || functionCode == 2)
|
||||
{
|
||||
readLength = MaxPack * 8 * 2;
|
||||
readLength = maxPack * 8 * 2;
|
||||
}
|
||||
//获取当前的一组打包地址信息,
|
||||
var tempAddressAndLen = orderByAddressAndLen.Where(t => (t.AddressStart + (t.Length / 2.0)) <= minAddress + readLength).ToList();
|
||||
//起始地址
|
||||
var startAddress = tempAddressAndLen.OrderBy(it => it.AddressStart).First();
|
||||
//读取寄存器长度
|
||||
var sourceLen = tempAddressAndLen.Last().AddressStart + (int)Math.Ceiling(tempAddressAndLen.Last().Length / 2.0) - startAddress.AddressStart;
|
||||
|
||||
DeviceVariableSourceRead sourceRead = new(timeInterval)
|
||||
|
||||
//获取当前的一组打包地址信息,
|
||||
var tempAddressEnd = orderByAddressEnd.Where(t => t.AddressEnd <= minAddress + readLength).ToList();
|
||||
//起始地址
|
||||
var startAddress = tempAddressEnd.OrderBy(it => it.AddressStart).First();
|
||||
//读取寄存器长度
|
||||
var sourceLen = tempAddressEnd.Last().AddressEnd - startAddress.AddressStart;
|
||||
|
||||
T sourceRead = new()
|
||||
{
|
||||
TimerTick = new TimerTick(intervalTime),
|
||||
//这里只需要根据地址排序的第一个地址,作为实际打包报文中的起始地址
|
||||
Address = startAddress.ToString(),
|
||||
VariableAddress = startAddress.ToString(),
|
||||
Length = sourceLen
|
||||
};
|
||||
foreach (var item in tempAddressAndLen)
|
||||
foreach (var item in tempAddressEnd)
|
||||
{
|
||||
var readNode = addressList[item];
|
||||
if ((functionCode == -1 || functionCode == 3 || functionCode == 4) &&
|
||||
readNode.DataTypeEnum == DataTypeEnum.Boolean)
|
||||
if ((functionCode == -1 || functionCode == 3 || functionCode == 4) && readNode.DataTypeEnum == DataTypeEnum.Boolean)
|
||||
{
|
||||
readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 16) + readNode.Index;
|
||||
}
|
||||
@@ -146,15 +153,13 @@ internal static class ModbusHelper
|
||||
readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 2) + readNode.Index;
|
||||
}
|
||||
|
||||
|
||||
sourceRead.DeviceVariables.Add(readNode);
|
||||
orderByAddressAndLen.Remove(item);
|
||||
orderByAddress.Remove(item);
|
||||
sourceRead.DeviceVariableRunTimes.Add(readNode);
|
||||
addressList.Remove(item);
|
||||
}
|
||||
|
||||
sourceReads.Add(sourceRead);
|
||||
if (orderByAddressAndLen.Count > 0)
|
||||
minAddress = orderByAddress.First().AddressStart;
|
||||
if (orderByAddressEnd.Count() > 0)
|
||||
minAddress = orderByAddressStart.First().AddressStart;
|
||||
else
|
||||
break;
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -14,11 +14,10 @@
|
||||
<inheritdoc/>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.#ctor(System.String,System.UInt16)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.#ctor(System.String,System.Byte)">
|
||||
<inheritdoc/>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.AddressStart">
|
||||
<summary>
|
||||
读取功能码
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ReadFunction">
|
||||
<summary>
|
||||
@@ -35,9 +34,29 @@
|
||||
写入功能码
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.Parse(System.String,System.Int32)">
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ByteLength">
|
||||
<summary>
|
||||
打包临时写入,需要读取的字节长度
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.AddressEnd">
|
||||
<summary>
|
||||
读取功能码
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.Parse(System.String)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ParseFrom(System.String,System.Byte)">
|
||||
<summary>
|
||||
解析地址
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ParseFrom(System.String,ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress)">
|
||||
<summary>
|
||||
解析地址
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ToString">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
@@ -127,24 +146,14 @@
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.#ctor(ThingsGateway.Foundation.TcpClientEx)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.#ctor(ThingsGateway.Foundation.Sockets.TcpClient)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.CacheTimeout">
|
||||
<summary>
|
||||
组包缓存时间/ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Crc16CheckEnable">
|
||||
<summary>
|
||||
Crc校验
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.FrameTime">
|
||||
<summary>
|
||||
帧前时间ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Station">
|
||||
<summary>
|
||||
站号
|
||||
@@ -153,10 +162,22 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.GetAddressDescription">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.SetDataAdapter">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.SetDataAdapter(System.Object)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
@@ -165,13 +186,10 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.#ctor(TouchSocket.Sockets.UdpSession)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.#ctor(ThingsGateway.Foundation.Sockets.UdpSession)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Crc16CheckEnable">
|
||||
@@ -179,11 +197,6 @@
|
||||
Crc校验
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.FrameTime">
|
||||
<summary>
|
||||
帧前时间ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Station">
|
||||
<summary>
|
||||
站号
|
||||
@@ -192,10 +205,22 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.GetAddressDescription">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.SetDataAdapter">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.SetDataAdapter(System.Object)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
@@ -204,9 +229,6 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdpDataHandleAdapter">
|
||||
<summary>
|
||||
<inheritdoc/>
|
||||
@@ -231,27 +253,17 @@
|
||||
ModbusRtu
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.#ctor(ThingsGateway.Foundation.Serial.SerialClient)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.#ctor(ThingsGateway.Foundation.Serial.SerialSession)">
|
||||
<summary>
|
||||
ModbusRtu
|
||||
</summary>
|
||||
<param name="serialClient"></param>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.CacheTimeout">
|
||||
<summary>
|
||||
组包缓存时间/ms
|
||||
</summary>
|
||||
<param name="serialSession"></param>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Crc16CheckEnable">
|
||||
<summary>
|
||||
Crc校验
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.FrameTime">
|
||||
<summary>
|
||||
帧前时间ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Station">
|
||||
<summary>
|
||||
站号
|
||||
@@ -260,10 +272,22 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.GetAddressDescription">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Read(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.SetDataAdapter">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.SetDataAdapter(System.Object)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
@@ -339,7 +363,7 @@
|
||||
接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.#ctor(TouchSocket.Sockets.TcpService)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.#ctor(ThingsGateway.Foundation.Sockets.TcpService)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.MulStation">
|
||||
@@ -352,13 +376,28 @@
|
||||
默认站点
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Dispose">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.GetAddressDescription">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Read(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.SetDataAdapter(TouchSocket.Sockets.SocketClient)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.SetDataAdapter(System.Object)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
@@ -367,10 +406,7 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Dispose(System.Boolean)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Received(TouchSocket.Sockets.SocketClient,TouchSocket.Sockets.IRequestInfo)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Received(ThingsGateway.Foundation.Sockets.SocketClient,ThingsGateway.Foundation.Core.IRequestInfo)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerDataHandleAdapter">
|
||||
@@ -406,6 +442,11 @@
|
||||
当前关联的地址
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.Length">
|
||||
<summary>
|
||||
当前读写的数据长度
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.HeadBytesLength">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
@@ -415,19 +456,9 @@
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.#ctor(ThingsGateway.Foundation.TcpClientEx)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.#ctor(ThingsGateway.Foundation.Sockets.TcpClient)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.CacheTimeout">
|
||||
<summary>
|
||||
组包缓存时间/ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.FrameTime">
|
||||
<summary>
|
||||
帧前时间ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.IsCheckMessageId">
|
||||
<summary>
|
||||
检测事务标识符
|
||||
@@ -441,10 +472,22 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.GetAddressDescription">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.SetDataAdapter">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.SetDataAdapter(System.Object)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
@@ -453,12 +496,9 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcpDataHandleAdapter">
|
||||
<summary>
|
||||
<inheritdoc/>
|
||||
ModbusTcpDataHandleAdapter
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcpDataHandleAdapter.IsCheckMessageId">
|
||||
@@ -494,14 +534,9 @@
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.#ctor(TouchSocket.Sockets.UdpSession)">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.#ctor(ThingsGateway.Foundation.Sockets.UdpSession)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.FrameTime">
|
||||
<summary>
|
||||
帧前时间ms
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.IsCheckMessageId">
|
||||
<summary>
|
||||
检测事务标识符
|
||||
@@ -512,13 +547,25 @@
|
||||
站号
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.GetAddressDescription">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.SetDataAdapter">
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.SetDataAdapter(System.Object)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
|
||||
@@ -527,9 +574,6 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter">
|
||||
<summary>
|
||||
<inheritdoc/>
|
||||
@@ -549,5 +593,19 @@
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter.UnpackResponse(System.Byte[],System.Byte[])">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:ThingsGateway.Foundation.Adapter.Modbus.PackHelper">
|
||||
<summary>
|
||||
PackHelper
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:ThingsGateway.Foundation.Adapter.Modbus.PackHelper.ModbusLoadSourceRead``2(ThingsGateway.Foundation.Core.IReadWrite,System.Collections.Generic.List{``1},System.Int32)">
|
||||
<summary>
|
||||
打包变量,添加到<see href="deviceVariableSourceReads"></see>
|
||||
</summary>
|
||||
<param name="device"></param>
|
||||
<param name="deviceVariables"></param>
|
||||
<param name="MaxPack">最大打包长度</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
@@ -10,7 +10,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
|
@@ -10,8 +10,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCDA.Da;
|
||||
|
||||
/// <summary>
|
@@ -10,10 +10,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
|
||||
|
||||
@@ -201,7 +198,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
OnWriteCompleted?.Invoke(itemwrite);
|
||||
}
|
||||
|
||||
internal OperResult AddOpcItem(OpcItem[] items)
|
||||
internal List<Tuple<OpcItem, int>> AddOpcItem(OpcItem[] items)
|
||||
{
|
||||
IntPtr pResults = IntPtr.Zero;
|
||||
IntPtr pErrors = IntPtr.Zero;
|
||||
@@ -228,7 +225,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
m_ItemManagement?.AddItems(items.Length, itemDefyArray, out pResults, out pErrors);
|
||||
IntPtr Pos = pResults;
|
||||
Marshal.Copy(pErrors, errors, 0, items.Length);
|
||||
StringBuilder stringBuilder = new();
|
||||
List<Tuple<OpcItem, int>> results = new();
|
||||
for (int j = 0; j < items.Length; j++)
|
||||
{
|
||||
if (errors[j] == 0)
|
||||
@@ -250,21 +247,10 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.AppendLine(items[j].ItemID + "添加失败,错误代码:" + errors[j]);
|
||||
results.Add(Tuple.Create(items[j], errors[j]));
|
||||
}
|
||||
}
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
return new OperResult(stringBuilder.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
return results;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -299,7 +285,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
/// 组读取
|
||||
/// </summary>
|
||||
/// <exception cref="ExternalException"></exception>
|
||||
internal OperResult ReadAsync()
|
||||
internal void ReadAsync()
|
||||
{
|
||||
IntPtr pErrors = IntPtr.Zero;
|
||||
try
|
||||
@@ -316,16 +302,11 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count);
|
||||
if (PErrors.Any(a => a > 0))
|
||||
{
|
||||
return new OperResult("读取错误,错误代码:" + pErrors);
|
||||
throw new("读取错误,错误代码:" + pErrors);
|
||||
}
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
else
|
||||
return new OperResult("连接无效");
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
throw new("连接无效");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -336,7 +317,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal OperResult RemoveItem(OpcItem[] items)
|
||||
internal List<Tuple<OpcItem, int>> RemoveItem(OpcItem[] items)
|
||||
{
|
||||
IntPtr pErrors = IntPtr.Zero;
|
||||
int[] errors = new int[items.Length];
|
||||
@@ -356,59 +337,42 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
{
|
||||
Marshal.FreeCoTaskMem(pErrors);
|
||||
}
|
||||
|
||||
}
|
||||
StringBuilder stringBuilder = new();
|
||||
List<Tuple<OpcItem, int>> results = new();
|
||||
for (int i = 0; i < errors.Length; i++)
|
||||
{
|
||||
if (errors[i] != 0)
|
||||
{
|
||||
stringBuilder.AppendLine(items[i].ItemID + "移除失败,错误代码:" + errors[i]);
|
||||
results.Add(Tuple.Create(items[i], errors[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
OpcItems.Remove(items[i]);
|
||||
}
|
||||
}
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
return new OperResult(stringBuilder.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
internal OperResult Write(object[] values, int[] serverHandle, out int[] errors)
|
||||
|
||||
|
||||
internal List<Tuple<int, int>> Write(object[] values, int[] serverHandle)
|
||||
{
|
||||
IntPtr pErrors = IntPtr.Zero;
|
||||
errors = new int[values.Length];
|
||||
var errors = new int[values.Length];
|
||||
if (m_Async2IO != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_SyncIO.Write(values.Length, serverHandle, values, out pErrors);
|
||||
Marshal.Copy(pErrors, errors, 0, values.Length);
|
||||
StringBuilder stringBuilder = new();
|
||||
List<Tuple<int, int>> results = new();
|
||||
for (int i = 0; i < errors.Length; i++)
|
||||
{
|
||||
if (errors[i] != 0)
|
||||
{
|
||||
stringBuilder.AppendLine("写入错误,错误代码:" + errors[i]);
|
||||
results.Add(Tuple.Create(serverHandle[i], errors[i]));
|
||||
}
|
||||
}
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
return new(stringBuilder.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
return results;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -419,7 +383,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
}
|
||||
}
|
||||
else
|
||||
return new OperResult("连接无效");
|
||||
throw new("连接无效");
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
@@ -465,7 +429,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private OperResult ActiveDataChanged(bool active)
|
||||
private void ActiveDataChanged(bool active)
|
||||
{
|
||||
IntPtr pRequestedUpdateRate = IntPtr.Zero;
|
||||
IntPtr hClientGroup = IntPtr.Zero;
|
||||
@@ -484,11 +448,6 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
|
||||
pDeadband,
|
||||
pLCID,
|
||||
hClientGroup);
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
finally
|
||||
{
|
@@ -10,8 +10,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
|
||||
@@ -51,58 +49,51 @@ internal class OpcServer : IDisposable
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal OperResult<OpcGroup> AddGroup(string groupName)
|
||||
internal OpcGroup AddGroup(string groupName)
|
||||
{
|
||||
return AddGroup(groupName, true, 1000, 0);
|
||||
}
|
||||
|
||||
/// <returns></returns>
|
||||
internal OperResult<OpcGroup> AddGroup(string groupName, bool active, int reqUpdateRate, float deadBand)
|
||||
internal OpcGroup AddGroup(string groupName, bool active, int reqUpdateRate, float deadBand)
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
return new OperResult<OpcGroup>("未初始化连接!");
|
||||
throw new("未初始化连接!");
|
||||
OpcGroup group = new(groupName, active, reqUpdateRate, deadBand);
|
||||
Guid riid = typeof(IOPCItemMgt).GUID;
|
||||
try
|
||||
m_OpcServer?.AddGroup(group.Name,
|
||||
group.IsActive ? 1 : 0,//IsActive
|
||||
group.RequestUpdateRate,//RequestedUpdateRate 1000ms
|
||||
group.ClientGroupHandle,
|
||||
group.TimeBias.AddrOfPinnedObject(),
|
||||
group.PercendDeadBand.AddrOfPinnedObject(),
|
||||
group.LCID,
|
||||
out group.serverGroupHandle,
|
||||
out group.revisedUpdateRate,
|
||||
ref riid,
|
||||
out group.groupPointer);
|
||||
if (group.groupPointer != null)
|
||||
{
|
||||
m_OpcServer?.AddGroup(group.Name,
|
||||
group.IsActive ? 1 : 0,//IsActive
|
||||
group.RequestUpdateRate,//RequestedUpdateRate 1000ms
|
||||
group.ClientGroupHandle,
|
||||
group.TimeBias.AddrOfPinnedObject(),
|
||||
group.PercendDeadBand.AddrOfPinnedObject(),
|
||||
group.LCID,
|
||||
out group.serverGroupHandle,
|
||||
out group.revisedUpdateRate,
|
||||
ref riid,
|
||||
out group.groupPointer);
|
||||
if (group.groupPointer != null)
|
||||
{
|
||||
group.InitIoInterfaces(group.groupPointer);
|
||||
OpcGroups.Add(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<OpcGroup>("添加OPC组错误,OPC服务器返回null");
|
||||
}
|
||||
return OperResult.CreateSuccessResult(group);
|
||||
group.InitIoInterfaces(group.groupPointer);
|
||||
OpcGroups.Add(group);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
return new OperResult<OpcGroup>(ex);
|
||||
throw new("添加OPC组错误,OPC服务器返回null");
|
||||
}
|
||||
return group;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取节点
|
||||
/// </summary>
|
||||
internal OperResult<List<BrowseElement>> Browse(string itemId = null)
|
||||
internal List<BrowseElement> Browse(string itemId = null)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
return new OperResult<List<BrowseElement>>("未初始化连接!");
|
||||
|
||||
throw new("未初始化连接!");
|
||||
|
||||
var count = 0;
|
||||
var moreElements = 0;
|
||||
@@ -118,108 +109,107 @@ internal class OpcServer : IDisposable
|
||||
new PropertyID(6),
|
||||
new PropertyID(101),
|
||||
};
|
||||
try
|
||||
{
|
||||
|
||||
var server = m_OpcServer as IOPCBrowse;
|
||||
server.Browse(
|
||||
string.IsNullOrEmpty(itemId) ? "" : itemId,
|
||||
ref pContinuationPoint,
|
||||
int.MaxValue,
|
||||
OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
1,
|
||||
filterId.Length,
|
||||
Interop.GetPropertyIDs(filterId),
|
||||
out moreElements,
|
||||
out count,
|
||||
out pElements);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<List<BrowseElement>>(ex);
|
||||
}
|
||||
|
||||
var server = m_OpcServer as IOPCBrowse;
|
||||
server.Browse(
|
||||
string.IsNullOrEmpty(itemId) ? "" : itemId,
|
||||
ref pContinuationPoint,
|
||||
int.MaxValue,
|
||||
OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
1,
|
||||
filterId.Length,
|
||||
Interop.GetPropertyIDs(filterId),
|
||||
out moreElements,
|
||||
out count,
|
||||
out pElements);
|
||||
BrowseElement[] browseElements = Interop.GetBrowseElements(ref pElements, count, true);
|
||||
string stringUni = Marshal.PtrToStringUni(pContinuationPoint);
|
||||
Marshal.FreeCoTaskMem(pContinuationPoint);
|
||||
this.ProcessResults(browseElements, filterId);
|
||||
return OperResult.CreateSuccessResult(browseElements?.ToList());
|
||||
return browseElements?.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
internal OperResult Connect()
|
||||
internal void Connect()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Host) && !string.IsNullOrEmpty(Name))
|
||||
{
|
||||
var info = Discovery.OpcDiscovery.GetOpcServer(Name, Host);
|
||||
if (!info.IsSuccess)
|
||||
{
|
||||
return info;
|
||||
}
|
||||
object o = Comn.ComInterop.CreateInstance(info.Content.CLSID, Host);
|
||||
object o = Comn.ComInterop.CreateInstance(info.CLSID, Host);
|
||||
if (o == null)
|
||||
{
|
||||
return new(string.Format("{0}{1}无法创建com对象", info.Content.CLSID, Host));
|
||||
throw new(string.Format("{0}{1}无法创建com对象", info.CLSID, Host));
|
||||
}
|
||||
m_OpcServer = (IOPCServer)o;
|
||||
IsConnected = true;
|
||||
return OperResult.CreateSuccessResult();
|
||||
}
|
||||
return new("应初始化Host与Name");
|
||||
else
|
||||
throw new("应初始化Host与Name");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务器状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal OperResult<ServerStatus> GetServerStatus()
|
||||
internal ServerStatus GetServerStatus()
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
return new OperResult<ServerStatus>("未初始化连接!");
|
||||
IntPtr statusPtr = IntPtr.Zero;
|
||||
ServerStatus serverStatus = null;
|
||||
try
|
||||
{
|
||||
if (null == m_OpcServer || IsConnected == false)
|
||||
throw new("未初始化连接!");
|
||||
IntPtr statusPtr = IntPtr.Zero;
|
||||
m_OpcServer?.GetStatus(out statusPtr);
|
||||
OPCSERVERSTATUS status;
|
||||
ServerStatus = new ServerStatus();
|
||||
if (statusPtr != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
|
||||
object o = Marshal.PtrToStructure(statusPtr, typeof(OPCSERVERSTATUS));
|
||||
|
||||
Marshal.FreeCoTaskMem(statusPtr);
|
||||
|
||||
if (o != null)
|
||||
{
|
||||
object o = Marshal.PtrToStructure(statusPtr, typeof(OPCSERVERSTATUS));
|
||||
if (null != o)
|
||||
{
|
||||
status = (OPCSERVERSTATUS)o;
|
||||
ServerStatus.Version = status.wMajorVersion.ToString() + "." + status.wMinorVersion.ToString() + "." + status.wBuildNumber.ToString();
|
||||
ServerStatus.ServerState = status.dwServerState;
|
||||
ServerStatus.StartTime = Comn.Convert.FileTimeToDateTime(status.ftStartTime);
|
||||
ServerStatus.CurrentTime = Comn.Convert.FileTimeToDateTime(status.ftCurrentTime);
|
||||
ServerStatus.LastUpdateTime = Comn.Convert.FileTimeToDateTime(status.ftLastUpdateTime);
|
||||
ServerStatus.VendorInfo = status.szVendorInfo;
|
||||
}
|
||||
status = (OPCSERVERSTATUS)o;
|
||||
serverStatus = new();
|
||||
serverStatus.Version = $"{status.wMajorVersion.ToString()}.{status.wMinorVersion.ToString()}.{status.wBuildNumber.ToString()}";
|
||||
serverStatus.ServerState = status.dwServerState;
|
||||
serverStatus.StartTime = Comn.Convert.FileTimeToDateTime(status.ftStartTime);
|
||||
serverStatus.CurrentTime = Comn.Convert.FileTimeToDateTime(status.ftCurrentTime);
|
||||
serverStatus.LastUpdateTime = Comn.Convert.FileTimeToDateTime(status.ftLastUpdateTime);
|
||||
serverStatus.VendorInfo = status.szVendorInfo;
|
||||
IsConnected = true;
|
||||
return OperResult.CreateSuccessResult(ServerStatus);
|
||||
|
||||
return serverStatus;
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
IsConnected = false;
|
||||
return new OperResult<ServerStatus>(ex);
|
||||
throw new("未知错误");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
IsConnected = false;
|
||||
return new OperResult<ServerStatus>("获取状态失败");
|
||||
throw new("未知错误");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
finally
|
||||
{
|
||||
IsConnected = false;
|
||||
return new OperResult<ServerStatus>(ex);
|
||||
if (serverStatus != null)
|
||||
IsConnected = true;
|
||||
else
|
||||
IsConnected = false;
|
||||
ServerStatus = serverStatus;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal void RemoveGroup(OpcGroup group)
|
@@ -10,15 +10,11 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCDA.Discovery;
|
||||
/// <summary>
|
||||
/// OpcDiscovery
|
||||
@@ -38,58 +34,58 @@ public class OpcDiscovery
|
||||
/// <param name="serverName"></param>
|
||||
/// <param name="host"></param>
|
||||
/// <returns></returns>
|
||||
internal static OperResult<ServerInfo> GetOpcServer(string serverName, string host)
|
||||
internal static ServerInfo GetOpcServer(string serverName, string host)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(serverName))
|
||||
{
|
||||
throw new("检索失败,需提供OPCName");
|
||||
}
|
||||
ServerInfo result = null;
|
||||
ServerInfo[] serverInfos = null;
|
||||
object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host);
|
||||
if (o_Server == null)
|
||||
throw new("检索失败,请检查是否安装OPC Runtime");
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(serverName))
|
||||
{
|
||||
return new OperResult<ServerInfo>("检索失败,需提供OPCName");
|
||||
}
|
||||
ServerInfo result = null;
|
||||
ServerInfo[] serverInfos = null;
|
||||
object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host);
|
||||
if (o_Server == null)
|
||||
return new OperResult<ServerInfo>("检索失败,请检查是否安装OPC Runtime");
|
||||
Guid catid = CATID_OPC_DA20;
|
||||
|
||||
//两种方式,兼容国产部分OPCServer不支持IOPCServerList2的情况
|
||||
try
|
||||
{
|
||||
Guid catid = CATID_OPC_DA20;
|
||||
|
||||
//两种方式,兼容国产部分OPCServer不支持IOPCServerList2的情况
|
||||
try
|
||||
{
|
||||
IOPCServerList2 m_server2 = (IOPCServerList2)o_Server;
|
||||
GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server2, catid);
|
||||
if (result == null)
|
||||
{
|
||||
IOPCServerList m_server = (IOPCServerList)o_Server;
|
||||
GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
IOPCServerList2 m_server2 = (IOPCServerList2)o_Server;
|
||||
GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server2, catid);
|
||||
if (result == null)
|
||||
{
|
||||
IOPCServerList m_server = (IOPCServerList)o_Server;
|
||||
GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
return new OperResult<ServerInfo>($"无法创建OPCServer连接,请检查OPC名称是否一致,以下为Host{host}中的OPC列表:"
|
||||
+ Environment.NewLine +
|
||||
JToken.Parse(serverInfos.ToJson()).ToString()
|
||||
);
|
||||
}
|
||||
return OperResult.CreateSuccessResult(result);
|
||||
}
|
||||
finally
|
||||
catch
|
||||
{
|
||||
Comn.ComInterop.RealseComServer(o_Server);
|
||||
o_Server = null;
|
||||
IOPCServerList m_server = (IOPCServerList)o_Server;
|
||||
GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid);
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var item in serverInfos)
|
||||
{
|
||||
sb.AppendLine(item.ToString());
|
||||
}
|
||||
throw new($"无法创建OPCServer连接,请检查OPC名称是否一致,以下为{host}中的OPC列表:"
|
||||
+ Environment.NewLine +
|
||||
sb.ToString()
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
finally
|
||||
{
|
||||
return new OperResult<ServerInfo>(ex);
|
||||
Comn.ComInterop.RealseComServer(o_Server);
|
||||
o_Server = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void GetIOPCServerList(ref ServerInfo result, ref ServerInfo[] serverInfos, string serverName, string host, IOPCServerList m_server, Guid catid)
|
||||
@@ -246,4 +242,15 @@ internal class ServerInfo
|
||||
internal string Host { get; set; } = string.Empty;
|
||||
internal string ProgID { get; set; } = string.Empty;
|
||||
internal string VerIndProgID { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine($"{nameof(CLSID)}:{CLSID}");
|
||||
stringBuilder.AppendLine($"{nameof(Description)}:{Description}");
|
||||
stringBuilder.AppendLine($"{nameof(Host)}:{Host}");
|
||||
stringBuilder.AppendLine($"{nameof(ProgID)}:{ProgID}");
|
||||
stringBuilder.AppendLine($"{nameof(VerIndProgID)}:{VerIndProgID}");
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
@@ -799,7 +799,7 @@ public class Interop
|
||||
if (input.GetType() == typeof(DateTime))
|
||||
{
|
||||
DateTime dateTime = (DateTime)input;
|
||||
return dateTime != DateTime.MinValue ? (object)dateTime.ToUniversalTime() : (object)dateTime;
|
||||
return dateTime != DateTime.MinValue ? (object)dateTime.ToLocalTime() : (object)dateTime;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,9 +10,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCDA;
|
||||
|
||||
internal static class CollectionExtensions
|
||||
@@ -23,11 +20,30 @@ internal static class CollectionExtensions
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="this"></param>
|
||||
/// <param name="where"></param>
|
||||
public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
|
||||
internal static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
|
||||
{
|
||||
foreach (var obj in @this.Where(where).ToList())
|
||||
{
|
||||
@this.Remove(obj);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 将项目列表分解为特定大小的块
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="chunksize"></param>
|
||||
/// <returns></returns>
|
||||
internal static List<List<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
|
||||
{
|
||||
var pos = 0;
|
||||
List<List<T>> n = new();
|
||||
while (source.Skip(pos).Any())
|
||||
{
|
||||
n.Add(source.Skip(pos).Take(chunksize).ToList());
|
||||
pos += chunksize;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCDA;
|
||||
|
||||
/// <summary>
|
||||
/// DictionaryExtension
|
||||
/// </summary>
|
||||
internal static class DictionaryExtension
|
||||
{
|
||||
#region 字典扩展
|
||||
|
||||
/// <summary>
|
||||
/// 移除满足条件的项目。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="pairs"></param>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
|
||||
{
|
||||
var list = new List<TKey>();
|
||||
foreach (var item in pairs)
|
||||
{
|
||||
if (func?.Invoke(item) == true)
|
||||
{
|
||||
list.Add(item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (pairs.TryRemove(item, out _))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
dictionary.Add(tkey, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
dictionary[tkey] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(tkey, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值。如果键不存在,则返回默认值。
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <returns></returns>
|
||||
internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
|
||||
{
|
||||
return dictionary.TryGetValue(tkey, out var value) ? value : default;
|
||||
}
|
||||
#endregion 字典扩展
|
||||
}
|
@@ -13,5 +13,5 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Threading;
|
||||
|
@@ -10,20 +10,12 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
|
||||
using ThingsGateway.Foundation.Adapter.OPCDA.Da;
|
||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
@@ -38,30 +30,29 @@ public delegate void DataChangedEventHandler(List<ItemReadResult> values);
|
||||
/// <summary>
|
||||
/// OPCDAClient
|
||||
/// </summary>
|
||||
public class OPCDAClient : DisposableObject
|
||||
public class OPCDAClient : IDisposable
|
||||
{
|
||||
private readonly ILog _logger;
|
||||
/// <summary>
|
||||
/// LogAction
|
||||
/// </summary>
|
||||
private readonly Action<byte, object, string, Exception> _logAction;
|
||||
|
||||
private readonly EasyLock checkLock = new();
|
||||
private readonly object checkLock = new();
|
||||
|
||||
private Timer checkTimer;
|
||||
|
||||
private bool FirstConnect;
|
||||
|
||||
private int IsExit = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 当前保存的需订阅列表
|
||||
/// </summary>
|
||||
private Dictionary<string, List<OpcItem>> ItemDicts = new();
|
||||
|
||||
private OpcServer m_server;
|
||||
|
||||
private bool publicConnect;
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public OPCDAClient(ILog logger)
|
||||
public OPCDAClient(Action<byte, object, string, Exception> logAction)
|
||||
{
|
||||
#if (NET6_0_OR_GREATER)
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
@@ -69,7 +60,7 @@ public class OPCDAClient : DisposableObject
|
||||
throw new NotSupportedException("不支持非windows系统");
|
||||
}
|
||||
#endif
|
||||
_logger = logger;
|
||||
_logAction = logAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,68 +78,56 @@ public class OPCDAClient : DisposableObject
|
||||
/// </summary>
|
||||
public OPCNode OPCNode { get; private set; }
|
||||
private List<OpcGroup> Groups => m_server.OpcGroups;
|
||||
|
||||
/// <summary>
|
||||
/// 添加节点,需要在连接成功后执行
|
||||
/// </summary>
|
||||
/// <param name="items">组名称/变量节点,注意每次添加的组名称不能相同</param>
|
||||
public OperResult AddItems(Dictionary<string, List<OpcItem>> items)
|
||||
public void AddItems(Dictionary<string, List<OpcItem>> items)
|
||||
{
|
||||
try
|
||||
if (IsExit == 1) throw new("对象已释放");
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (IsExit == 1) return new("对象已释放");
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (var item in items)
|
||||
if (IsExit == 1) throw new("对象已释放");
|
||||
try
|
||||
{
|
||||
if (IsExit == 1) return new("对象已释放");
|
||||
var subscription = m_server.AddGroup(item.Key, true, OPCNode.UpdateRate, OPCNode.DeadBand);
|
||||
if (subscription.IsSuccess)
|
||||
{
|
||||
subscription.Content.ActiveSubscribe = OPCNode.ActiveSubscribe;
|
||||
subscription.Content.OnDataChanged += Subscription_OnDataChanged;
|
||||
subscription.Content.OnReadCompleted += Subscription_OnDataChanged;
|
||||
subscription.ActiveSubscribe = OPCNode.ActiveSubscribe;
|
||||
subscription.OnDataChanged += Subscription_OnDataChanged;
|
||||
subscription.OnReadCompleted += Subscription_OnDataChanged;
|
||||
|
||||
var result = subscription.Content.AddOpcItem(item.Value.ToArray());
|
||||
if (!result.IsSuccess)
|
||||
var result = subscription.AddOpcItem(item.Value.ToArray());
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (result.Count > 0)
|
||||
{
|
||||
foreach (var item1 in result)
|
||||
{
|
||||
stringBuilder.AppendLine("添加变量失败" + result.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemDicts.AddOrUpdate(item.Key, item.Value);
|
||||
_logger?.Debug($"添加成功");
|
||||
stringBuilder.Append($"{item1.Item1.ItemID}:{item1.Item2}");
|
||||
}
|
||||
_logAction?.Invoke(3, this, $"添加变量失败:{stringBuilder}", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.AppendLine("添加组失败" + subscription.Message);
|
||||
ItemDicts.AddOrUpdate(item.Key, item.Value.Where(a => !result.Select(b => b.Item1).Contains(a)).ToList());
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < Groups?.Count; i++)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var group = Groups[i];
|
||||
if (group != null)
|
||||
{
|
||||
if (group.OpcItems.Count == 0)
|
||||
{
|
||||
ItemDicts.Remove(group.Name);
|
||||
m_server.RemoveGroup(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stringBuilder.Length > 0)
|
||||
{
|
||||
return new(stringBuilder.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult();
|
||||
_logAction?.Invoke(3, this, $"添加组失败:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
for (int i = 0; i < Groups?.Count; i++)
|
||||
{
|
||||
return new(ex);
|
||||
var group = Groups[i];
|
||||
if (group != null)
|
||||
{
|
||||
if (group.OpcItems.Count == 0)
|
||||
{
|
||||
ItemDicts.Remove(group.Name);
|
||||
m_server.RemoveGroup(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,8 +138,7 @@ public class OPCDAClient : DisposableObject
|
||||
public Dictionary<string, List<OpcItem>> AddItemsWithSave(List<string> items)
|
||||
{
|
||||
int i = 0;
|
||||
ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)
|
||||
).ChunkTrivialBetter(OPCNode.GroupSize).ToDictionary(a => "default" + (i++));
|
||||
ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OPCNode.GroupSize).ToDictionary(a => "default" + (i++));
|
||||
return ItemDicts;
|
||||
}
|
||||
|
||||
@@ -169,9 +147,9 @@ public class OPCDAClient : DisposableObject
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
publicConnect = true;
|
||||
Interlocked.CompareExchange(ref IsExit, 0, 1);
|
||||
PrivateConnect();
|
||||
FirstConnect = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -183,12 +161,26 @@ public class OPCDAClient : DisposableObject
|
||||
PrivateDisconnect();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
PrivateDisconnect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
|
||||
}
|
||||
Interlocked.CompareExchange(ref IsExit, 1, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浏览节点
|
||||
/// </summary>
|
||||
/// <param name="itemId"></param>
|
||||
/// <returns></returns>
|
||||
public OperResult<List<BrowseElement>> GetBrowseElements(string itemId = null)
|
||||
public List<BrowseElement> GetBrowseElements(string itemId = null)
|
||||
{
|
||||
return this.m_server?.Browse(itemId);
|
||||
}
|
||||
@@ -197,7 +189,7 @@ public class OPCDAClient : DisposableObject
|
||||
/// 获取服务状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public OperResult<ServerStatus> GetServerStatus()
|
||||
public ServerStatus GetServerStatus()
|
||||
{
|
||||
return this.m_server?.GetServerStatus();
|
||||
}
|
||||
@@ -211,11 +203,18 @@ public class OPCDAClient : DisposableObject
|
||||
if (node != null)
|
||||
OPCNode = node;
|
||||
checkTimer?.Stop();
|
||||
checkTimer?.SafeDispose();
|
||||
checkTimer?.Dispose();
|
||||
checkTimer = new Timer(OPCNode.CheckRate * 60 * 1000);
|
||||
checkTimer.Elapsed += CheckTimer_Elapsed;
|
||||
checkTimer.Start();
|
||||
m_server?.SafeDispose();
|
||||
try
|
||||
{
|
||||
m_server?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
|
||||
}
|
||||
m_server = new OpcServer(OPCNode.OPCName, OPCNode.OPCIP);
|
||||
}
|
||||
|
||||
@@ -224,32 +223,18 @@ public class OPCDAClient : DisposableObject
|
||||
/// </summary>
|
||||
/// <param name="groupName">组名称,值为null时读取全部组</param>
|
||||
/// <returns></returns>
|
||||
public OperResult ReadItemsWithGroup(string groupName = null)
|
||||
public void ReadItemsWithGroup(string groupName = null)
|
||||
{
|
||||
try
|
||||
PrivateConnect();
|
||||
{
|
||||
if (PrivateConnect())
|
||||
var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups;
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups;
|
||||
foreach (var group in groups)
|
||||
if (group.OpcItems.Count > 0)
|
||||
{
|
||||
if (group.OpcItems.Count > 0)
|
||||
{
|
||||
return group.ReadAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult("不存在任何变量");
|
||||
}
|
||||
group.ReadAsync();
|
||||
}
|
||||
return new OperResult("不存在任何变量");
|
||||
}
|
||||
return new OperResult("未初始化连接");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,23 +248,11 @@ public class OPCDAClient : DisposableObject
|
||||
{
|
||||
if (IsExit == 1) return;
|
||||
var opcGroups = Groups.Where(it => it.OpcItems.Any(a => a.ItemID == item));
|
||||
if (!opcGroups.Any())
|
||||
{
|
||||
_logger.Warning("找不到变量" + item);
|
||||
continue;
|
||||
}
|
||||
foreach (var opcGroup in opcGroups)
|
||||
{
|
||||
var tag = opcGroup.OpcItems.Where(a => item == a.ItemID);
|
||||
var result = opcGroup.RemoveItem(tag.ToArray());
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_logger.Warning($"移除变量{item}-" + result.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Debug($"移除变量{item}成功");
|
||||
}
|
||||
|
||||
if (opcGroup.OpcItems.Count == 0)
|
||||
{
|
||||
opcGroup.OnDataChanged -= Subscription_OnDataChanged;
|
||||
@@ -288,7 +261,7 @@ public class OPCDAClient : DisposableObject
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemDicts[opcGroup.Name].RemoveWhere(a => tag.Contains(a));
|
||||
ItemDicts[opcGroup.Name].RemoveWhere(a => tag.Contains(a) && !result.Select(b => b.Item1).Contains(a));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,83 +275,94 @@ public class OPCDAClient : DisposableObject
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入值
|
||||
/// 批量写入值
|
||||
/// </summary>
|
||||
/// <param name="valueName">写入</param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public OperResult WriteItem(string valueName, JToken value)
|
||||
public Dictionary<string, Tuple<bool, string>> WriteItem(Dictionary<string, object> writeInfos)
|
||||
{
|
||||
if (PrivateConnect())
|
||||
PrivateConnect();
|
||||
Dictionary<string, Tuple<bool, string>> results = new();
|
||||
|
||||
var valueGroup = writeInfos.GroupBy(itemId =>
|
||||
{
|
||||
var group = Groups.FirstOrDefault(it => it.OpcItems.Any(a => a.ItemID == itemId.Key));
|
||||
return group;
|
||||
}).ToList();
|
||||
|
||||
foreach (var item1 in valueGroup)
|
||||
{
|
||||
try
|
||||
{
|
||||
var group = Groups.FirstOrDefault(it => it.OpcItems.Any(a => a.ItemID == valueName));
|
||||
if (group == null)
|
||||
return new OperResult("不存在该变量" + valueName);
|
||||
var item = group.OpcItems.Where(it => it.ItemID == valueName).FirstOrDefault();
|
||||
int[] serverHandle = new int[1] { item.ServerHandle };
|
||||
int[] PErrors = new int[1];
|
||||
var jtoken = value;
|
||||
var rank = jtoken.CalculateActualValueRank();
|
||||
object rawWriteValue;
|
||||
switch (rank)
|
||||
if (item1.Key == null)
|
||||
{
|
||||
case -1:
|
||||
rawWriteValue = ((JValue)jtoken).Value;
|
||||
break;
|
||||
default:
|
||||
var jarray = ((JArray)jtoken);
|
||||
rawWriteValue = jarray.Select(j => (object)j).ToArray();
|
||||
break;
|
||||
}
|
||||
|
||||
object[] Value = new object[1] { rawWriteValue };
|
||||
var result = group.Write(Value, serverHandle, out PErrors);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex.Message);
|
||||
}
|
||||
}
|
||||
return new OperResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
PrivateDisconnect();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void CheckTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (checkLock.IsWaitting) return;
|
||||
checkLock.Wait();
|
||||
try
|
||||
{
|
||||
if (IsExit == 0)
|
||||
{
|
||||
var status = m_server.GetServerStatus();
|
||||
if (status.IsSuccess)
|
||||
{
|
||||
_logger?.Trace(OPCNode.ToString() + "OPC状态检查正常!");
|
||||
foreach (var item2 in item1)
|
||||
{
|
||||
results.AddOrUpdate(item2.Key, Tuple.Create(true, $"不存在该变量{item2.Key}"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsExit == 0 && FirstConnect)
|
||||
List<int> serverHandles = new();
|
||||
Dictionary<int, OpcItem> handleItems = new();
|
||||
List<object> values = new();
|
||||
foreach (var item2 in item1)
|
||||
{
|
||||
if (PrivateConnect())
|
||||
{
|
||||
_logger?.Warning(OPCNode.ToString() + "OPC重新链接成功!");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
var opcItem = item1.Key.OpcItems.Where(it => it.ItemID == item2.Key).FirstOrDefault();
|
||||
serverHandles.Add(opcItem.ServerHandle);
|
||||
handleItems.AddOrUpdate(opcItem.ServerHandle, opcItem);
|
||||
var rawWriteValue = item2.Value;
|
||||
values.Add(rawWriteValue);
|
||||
}
|
||||
|
||||
var result = item1.Key.Write(values.ToArray(), serverHandles.ToArray());
|
||||
var data = item1.ToList();
|
||||
foreach (var item2 in result)
|
||||
{
|
||||
results.AddOrUpdate(handleItems[item2.Item1].ItemID, Tuple.Create(true, $"错误代码{item2.Item2}"));
|
||||
}
|
||||
}
|
||||
foreach (var item2 in item1)
|
||||
{
|
||||
results.AddOrUpdate(item2.Key, Tuple.Create(false, $"成功"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var keys = writeInfos.Keys.ToList();
|
||||
foreach (var item in keys)
|
||||
{
|
||||
results.AddOrUpdate(item, Tuple.Create(true, ex.Message));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
private void CheckTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
lock (checkLock)
|
||||
{
|
||||
|
||||
if (IsExit == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = m_server.GetServerStatus();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (IsExit == 0 && publicConnect)
|
||||
{
|
||||
try
|
||||
{
|
||||
PrivateConnect();
|
||||
_logAction?.Invoke(1, this, $"重新链接成功", null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logAction?.Invoke(3, this, $"重新链接失败:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -387,103 +371,81 @@ public class OPCDAClient : DisposableObject
|
||||
timeer.Enabled = false;
|
||||
timeer.Stop();
|
||||
}
|
||||
}
|
||||
finally { checkLock.Release(); }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void PrivateAddItems()
|
||||
{
|
||||
var result = AddItems(ItemDicts);
|
||||
if (!result.IsSuccess)
|
||||
try
|
||||
{
|
||||
_logger.Warning(result.Message);
|
||||
AddItems(ItemDicts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logAction?.Invoke(3, this, $"添加点位失败:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
private bool PrivateConnect()
|
||||
private void PrivateConnect()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
try
|
||||
|
||||
if (m_server?.IsConnected == true)
|
||||
{
|
||||
if (m_server?.IsConnected == true)
|
||||
try
|
||||
{
|
||||
var status = m_server.GetServerStatus();
|
||||
if (!status.IsSuccess)
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var status1 = m_server.GetServerStatus();
|
||||
if (!status1.IsSuccess)
|
||||
{
|
||||
_logger?.Error(status1.Message);
|
||||
//失败重新连接
|
||||
try
|
||||
{
|
||||
//disconnect();
|
||||
Init(OPCNode);
|
||||
_logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 正在连接");
|
||||
var result = m_server?.Connect();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 连接成功");
|
||||
PrivateAddItems();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Error(result.Message);
|
||||
return IsConnected;
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
_logger?.Exception(ex2);
|
||||
return IsConnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//disconnect();
|
||||
Init(OPCNode);
|
||||
_logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 正在连接");
|
||||
var result = m_server?.Connect();
|
||||
if (result.IsSuccess)
|
||||
catch
|
||||
{
|
||||
_logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 连接成功");
|
||||
|
||||
Init(OPCNode);
|
||||
m_server?.Connect();
|
||||
_logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
|
||||
PrivateAddItems();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Error(result.Message);
|
||||
return IsConnected;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
_logger?.Exception(OPCNode.ToString(), ex);
|
||||
return false;
|
||||
Init(OPCNode);
|
||||
m_server?.Connect();
|
||||
_logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
|
||||
PrivateAddItems();
|
||||
}
|
||||
return IsConnected;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void PrivateDisconnect()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (IsConnected)
|
||||
_logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 断开连接");
|
||||
checkTimer.Enabled = false;
|
||||
checkTimer.Stop();
|
||||
_logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 断开连接", null);
|
||||
if (checkTimer != null)
|
||||
{
|
||||
checkTimer.Enabled = false;
|
||||
checkTimer.Stop();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_server?.SafeDispose();
|
||||
m_server?.Dispose();
|
||||
m_server = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Exception(ToString(), ex);
|
||||
_logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ public class OPCNode
|
||||
/// <summary>
|
||||
/// 是否订阅
|
||||
/// </summary>
|
||||
[Description("ActiveSubscribe")]
|
||||
[Description("订阅")]
|
||||
public bool ActiveSubscribe { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 内部检测重连间隔/min
|
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
|
||||
</Project>
|
@@ -1,4 +1,4 @@
|
||||
#region copyright
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
@@ -34,6 +34,11 @@ public class OPCNode
|
||||
/// </summary>
|
||||
[Description("登录密码")]
|
||||
public string Password { get; set; }
|
||||
/// <summary>
|
||||
/// 检查域
|
||||
/// </summary>
|
||||
[Description("检查域")]
|
||||
public bool CheckDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新间隔
|
||||
@@ -65,6 +70,11 @@ public class OPCNode
|
||||
/// </summary>
|
||||
[Description("安全策略")]
|
||||
public bool IsUseSecurity { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 加载服务端数据类型
|
||||
/// </summary>
|
||||
[Description("加载服务端数据类型")]
|
||||
public bool LoadType { get; set; } = true;
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
@@ -1,4 +1,5 @@
|
||||
#region copyright
|
||||
#region copyright
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
@@ -8,37 +9,33 @@
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#endregion
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using Opc.Ua.Client.ComplexTypes;
|
||||
using Opc.Ua.Configuration;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
|
||||
|
||||
//修改自https://github.com/dathlin/OpcUaHelper 与OPC基金会net库
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅委托
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public delegate void DataChangedEventHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) value);
|
||||
|
||||
/// <summary>
|
||||
/// OPCUAClient
|
||||
/// </summary>
|
||||
public class OPCUAClient : DisposableObject
|
||||
public class OPCUAClient : IDisposable
|
||||
{
|
||||
|
||||
#region 属性,变量等
|
||||
|
||||
/// <summary>
|
||||
/// 当前配置
|
||||
/// </summary>
|
||||
@@ -58,35 +55,33 @@ public class OPCUAClient : DisposableObject
|
||||
/// 当前的变量名称/OPC变量节点
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, VariableNode> _variableDicts = new();
|
||||
private readonly EasyLock checkLock = new();
|
||||
|
||||
private readonly object checkLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前的订阅组,组名称/组
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Subscription> dic_subscriptions = new();
|
||||
|
||||
private readonly ILog Log;
|
||||
private readonly ApplicationInstance m_application = new();
|
||||
|
||||
private readonly ApplicationConfiguration m_configuration;
|
||||
|
||||
private SessionReconnectHandler m_reConnectHandler;
|
||||
private EventHandler m_ReconnectComplete;
|
||||
private EventHandler m_ReconnectStarting;
|
||||
private EventHandler<KeepAliveEventArgs> m_KeepAliveComplete;
|
||||
private EventHandler<bool> m_ConnectComplete;
|
||||
private EventHandler<OpcUaStatusEventArgs> m_OpcStatusChange;
|
||||
|
||||
private ISession m_session;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的构造函数,实例化一个新的OPC UA类
|
||||
/// </summary>
|
||||
public OPCUAClient(ILog log)
|
||||
public OPCUAClient()
|
||||
{
|
||||
Log = log;
|
||||
var certificateValidator = new CertificateValidator();
|
||||
certificateValidator.CertificateValidation += CertificateValidation;
|
||||
SecurityConfiguration securityConfigurationcv = new()
|
||||
{
|
||||
UseValidatedCertificates = true,
|
||||
AutoAcceptUntrustedCertificates = true,//自动接受证书
|
||||
RejectSHA1SignedCertificates = false,
|
||||
MinimumCertificateKeySize = 1024,
|
||||
};
|
||||
certificateValidator.Update(securityConfigurationcv);
|
||||
|
||||
// 构建应用程序配置
|
||||
m_configuration = new ApplicationConfiguration
|
||||
@@ -103,12 +98,12 @@ public class OPCUAClient : DisposableObject
|
||||
MaxMessageQueueSize = 1000000,
|
||||
MaxNotificationQueueSize = 1000000,
|
||||
MaxPublishRequestCount = 10000000,
|
||||
|
||||
},
|
||||
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
AutoAcceptUntrustedCertificates = true,
|
||||
UseValidatedCertificates = true,
|
||||
AutoAcceptUntrustedCertificates = true,//自动接受证书
|
||||
RejectSHA1SignedCertificates = false,
|
||||
MinimumCertificateKeySize = 1024,
|
||||
SuppressNonceValidationErrors = true,
|
||||
@@ -145,8 +140,6 @@ public class OPCUAClient : DisposableObject
|
||||
StoreType = CertificateStoreType.Directory,
|
||||
StorePath = AppContext.BaseDirectory + @"OPCUAClientCertificate\pki\trustedUser",
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
TransportQuotas = new TransportQuotas
|
||||
@@ -168,10 +161,10 @@ public class OPCUAClient : DisposableObject
|
||||
DisableHiResClock = true
|
||||
};
|
||||
|
||||
certificateValidator.Update(m_configuration);
|
||||
|
||||
m_configuration.Validate(ApplicationType.Client);
|
||||
m_application.ApplicationConfiguration = m_configuration;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,6 +187,56 @@ public class OPCUAClient : DisposableObject
|
||||
/// </summary>
|
||||
public string OPCUAName { get; set; } = "ThingsGateway";
|
||||
|
||||
/// <summary>
|
||||
/// SessionReconnectHandler
|
||||
/// </summary>
|
||||
public SessionReconnectHandler ReConnectHandler => m_reConnectHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a good keep alive from the server arrives.
|
||||
/// </summary>
|
||||
public event EventHandler<KeepAliveEventArgs> KeepAliveComplete
|
||||
{
|
||||
add { m_KeepAliveComplete += value; }
|
||||
remove { m_KeepAliveComplete -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a reconnect operation starts.
|
||||
/// </summary>
|
||||
public event EventHandler ReconnectStarting
|
||||
{
|
||||
add { m_ReconnectStarting += value; }
|
||||
remove { m_ReconnectStarting -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a reconnect operation completes.
|
||||
/// </summary>
|
||||
public event EventHandler ReconnectComplete
|
||||
{
|
||||
add { m_ReconnectComplete += value; }
|
||||
remove { m_ReconnectComplete -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised after successfully connecting to or disconnecing from a server.
|
||||
/// </summary>
|
||||
public event EventHandler<bool> ConnectComplete
|
||||
{
|
||||
add { m_ConnectComplete += value; }
|
||||
remove { m_ConnectComplete -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised after the client status change
|
||||
/// </summary>
|
||||
public event EventHandler<OpcUaStatusEventArgs> OpcStatusChange
|
||||
{
|
||||
add { m_OpcStatusChange += value; }
|
||||
remove { m_OpcStatusChange -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前活动会话。
|
||||
/// </summary>
|
||||
@@ -204,9 +247,9 @@ public class OPCUAClient : DisposableObject
|
||||
#region 订阅
|
||||
|
||||
/// <summary>
|
||||
/// 新增订阅,需要指定订阅的关键字,订阅的tag名数组,以及回调方法
|
||||
/// 新增订阅,需要指定订阅组名称,订阅的tag名数组
|
||||
/// </summary>
|
||||
public void AddSubscription(string subscriptionName, string[] items)
|
||||
public async Task AddSubscriptionAsync(string subscriptionName, string[] items, bool loadType = true)
|
||||
{
|
||||
Subscription m_subscription = new(m_session.DefaultSubscription)
|
||||
{
|
||||
@@ -219,18 +262,26 @@ public class OPCUAClient : DisposableObject
|
||||
DisplayName = subscriptionName
|
||||
};
|
||||
List<MonitoredItem> monitoredItems = new();
|
||||
var variableNodes = loadType ? await ReadNodesAsync(items) : null;
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
var item = new MonitoredItem
|
||||
try
|
||||
{
|
||||
StartNodeId = new NodeId(items[i]),
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = items[i],
|
||||
Filter = new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
|
||||
SamplingInterval = OPCNode?.UpdateRate ?? 1000,
|
||||
};
|
||||
item.Notification += Callback;
|
||||
monitoredItems.Add(item);
|
||||
var item = new MonitoredItem
|
||||
{
|
||||
StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = items[i],
|
||||
Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
|
||||
SamplingInterval = OPCNode?.UpdateRate ?? 1000,
|
||||
};
|
||||
item.Notification += Callback;
|
||||
monitoredItems.Add(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"初始化{items[i]}变量订阅失败,错误原因:{ex}");
|
||||
}
|
||||
}
|
||||
m_subscription.AddItems(monitoredItems);
|
||||
|
||||
@@ -238,14 +289,16 @@ public class OPCUAClient : DisposableObject
|
||||
m_subscription.Create();
|
||||
foreach (var item in m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)))
|
||||
{
|
||||
item.Filter = new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
|
||||
item.Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
|
||||
}
|
||||
m_subscription.ApplyChanges();
|
||||
|
||||
var iserror = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
|
||||
if (iserror)
|
||||
var isError = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
|
||||
if (isError)
|
||||
{
|
||||
Log.Error("创建以下变量订阅失败" + m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)).Select(a => a.StartNodeId.ToString() + ":" + a.Status.Error.ToString()).ToJson());
|
||||
UpdateStatus(3, DateTime.Now, $"创建以下变量订阅失败:{Environment.NewLine}{m_subscription.MonitoredItems.Where(
|
||||
a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode))
|
||||
.Select(a => $"{a.StartNodeId.ToString()}:{a.Status.Error.ToString()}").ToJsonString()}");
|
||||
}
|
||||
|
||||
lock (dic_subscriptions)
|
||||
@@ -255,7 +308,7 @@ public class OPCUAClient : DisposableObject
|
||||
// remove
|
||||
dic_subscriptions[subscriptionName].Delete(true);
|
||||
m_session.RemoveSubscription(dic_subscriptions[subscriptionName]);
|
||||
dic_subscriptions[subscriptionName].SafeDispose();
|
||||
try { dic_subscriptions[subscriptionName].Dispose(); } catch { }
|
||||
dic_subscriptions[subscriptionName] = m_subscription;
|
||||
}
|
||||
else
|
||||
@@ -276,7 +329,7 @@ public class OPCUAClient : DisposableObject
|
||||
{
|
||||
item.Value.Delete(true);
|
||||
m_session.RemoveSubscription(item.Value);
|
||||
item.Value.SafeDispose();
|
||||
try { item.Value.Dispose(); } catch { }
|
||||
}
|
||||
dic_subscriptions.Clear();
|
||||
}
|
||||
@@ -295,33 +348,44 @@ public class OPCUAClient : DisposableObject
|
||||
// remove
|
||||
dic_subscriptions[subscriptionName].Delete(true);
|
||||
m_session.RemoveSubscription(dic_subscriptions[subscriptionName]);
|
||||
dic_subscriptions[subscriptionName].SafeDispose();
|
||||
try { dic_subscriptions[subscriptionName].Dispose(); } catch { }
|
||||
dic_subscriptions.RemoveWhere(a => a.Key == subscriptionName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void Callback(MonitoredItem monitoreditem, MonitoredItemNotificationEventArgs monitoredItemNotificationEventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
VariableNode variableNode = (VariableNode)ReadNode(monitoreditem.StartNodeId.ToString(), false);
|
||||
foreach (var value in monitoreditem.DequeueValues())
|
||||
if (m_session != null)
|
||||
{
|
||||
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
|
||||
if (data == null && value.Value != null)
|
||||
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
|
||||
foreach (var value in monitoreditem.DequeueValues())
|
||||
{
|
||||
Log.Warning($"{monitoreditem.StartNodeId}转换出错,原始值String为{value.Value}");
|
||||
if (value.Value != null)
|
||||
{
|
||||
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
|
||||
if (data == null && value.Value != null)
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}转换出错,原始值String为{value.Value}");
|
||||
var data1 = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
|
||||
}
|
||||
DataChangedHandler?.Invoke((variableNode, value, data));
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = JValue.CreateNull();
|
||||
DataChangedHandler?.Invoke((variableNode, value, data));
|
||||
}
|
||||
}
|
||||
DataChangedHandler?.Invoke((variableNode, value, data));
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"{monitoreditem.StartNodeId}订阅事件出错:{ex.Message}");
|
||||
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}订阅处理错误,错误原因:" + ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -401,9 +465,9 @@ public class OPCUAClient : DisposableObject
|
||||
/// <param name="end">结束时间</param>
|
||||
/// <param name="count">读取的个数</param>
|
||||
/// <param name="containBound">是否包含边界</param>
|
||||
/// <param name="token">token</param>
|
||||
/// <param name="cancellationToken">cancellationToken</param>
|
||||
/// <returns>读取的数据列表</returns>
|
||||
public async Task<List<DataValue>> ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false, CancellationToken token = default)
|
||||
public async Task<List<DataValue>> ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
HistoryReadValueId m_nodeToContinue = new()
|
||||
{
|
||||
@@ -430,7 +494,7 @@ public class OPCUAClient : DisposableObject
|
||||
TimestampsToReturn.Both,
|
||||
false,
|
||||
nodesToRead,
|
||||
token);
|
||||
cancellationToken);
|
||||
var results = result.Results;
|
||||
var diagnosticInfos = result.DiagnosticInfos;
|
||||
ClientBase.ValidateResponse(results, nodesToRead);
|
||||
@@ -447,14 +511,16 @@ public class OPCUAClient : DisposableObject
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 连接
|
||||
|
||||
private ComplexTypeSystem typeSystem;
|
||||
|
||||
/// <summary>
|
||||
/// 连接到服务器
|
||||
/// </summary>
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
m_session = await ConnectAsync(OPCNode.OPCUrl);
|
||||
await ConnectAsync(OPCNode.OPCUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -462,88 +528,167 @@ public class OPCUAClient : DisposableObject
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
// stop any reconnect operation.
|
||||
if (m_reConnectHandler != null)
|
||||
{
|
||||
m_reConnectHandler.SafeDispose();
|
||||
m_reConnectHandler = null;
|
||||
}
|
||||
|
||||
PrivateDisconnect();
|
||||
// disconnect any existing session.
|
||||
if (m_session != null)
|
||||
{
|
||||
Log.Debug("断开连接");
|
||||
m_session.Close(10000);
|
||||
m_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new session.
|
||||
/// </summary>
|
||||
/// <returns>The new session object.</returns>
|
||||
private async Task<ISession> ConnectAsync(string serverUrl)
|
||||
{
|
||||
PrivateDisconnect();
|
||||
|
||||
if (m_configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException("未初始化配置");
|
||||
}
|
||||
var useSecurity = OPCNode?.IsUseSecurity ?? true;
|
||||
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity, 10000);
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
|
||||
ConfiguredEndpoint endpoint = new(null, endpointDescription, endpointConfiguration);
|
||||
UserIdentity userIdentity;
|
||||
if (!string.IsNullOrEmpty(OPCNode.UserName))
|
||||
{
|
||||
userIdentity = new UserIdentity(OPCNode.UserName, OPCNode.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
userIdentity = new UserIdentity(new AnonymousIdentityToken());
|
||||
}
|
||||
//创建本地证书
|
||||
await m_application.CheckApplicationInstanceCertificate(true, 0, 1200);
|
||||
m_session = await Opc.Ua.Client.Session.Create(
|
||||
m_configuration,
|
||||
endpoint,
|
||||
false,
|
||||
OPCNode.CheckDomain,
|
||||
(string.IsNullOrEmpty(OPCUAName)) ? m_configuration.ApplicationName : OPCUAName,
|
||||
60000,
|
||||
userIdentity,
|
||||
Array.Empty<string>());
|
||||
typeSystem = new ComplexTypeSystem(m_session);
|
||||
|
||||
m_session.KeepAliveInterval = OPCNode.KeepAliveInterval == 0 ? 60000 : OPCNode.KeepAliveInterval;
|
||||
m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
|
||||
|
||||
// raise an event.
|
||||
DoConnectComplete(true);
|
||||
|
||||
UpdateStatus(2, DateTime.UtcNow, "Connected");
|
||||
|
||||
//如果是订阅模式,连接时添加订阅组
|
||||
if (OPCNode.ActiveSubscribe)
|
||||
await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray(), OPCNode.LoadType);
|
||||
return m_session;
|
||||
}
|
||||
|
||||
private void PrivateDisconnect()
|
||||
{
|
||||
bool state = m_session?.Connected == true;
|
||||
|
||||
|
||||
if (m_reConnectHandler != null)
|
||||
{
|
||||
try { m_reConnectHandler.Dispose(); } catch { }
|
||||
m_reConnectHandler = null;
|
||||
}
|
||||
if (m_session != null)
|
||||
{
|
||||
m_session.KeepAlive -= Session_KeepAlive;
|
||||
m_session.Close(10000);
|
||||
}
|
||||
|
||||
if (state)
|
||||
{
|
||||
UpdateStatus(2, DateTime.UtcNow, "Disconnected");
|
||||
DoConnectComplete(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 读取/写入
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器读取值
|
||||
/// </summary>
|
||||
public async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(string[] tags, CancellationToken token = default)
|
||||
public async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(string[] tags, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await ReadJTokenValueAsync(tags.Select(a => new NodeId(a)).ToArray(), token);
|
||||
var result = await ReadJTokenValueAsync(tags.Select(a => new NodeId(a)).ToArray(), cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步写opc标签
|
||||
/// </summary>
|
||||
public async Task<OperResult> WriteNodeAsync(string tag, JToken value, CancellationToken token = default)
|
||||
public async Task<Dictionary<string, Tuple<bool, string>>> WriteNodeAsync(Dictionary<string, JToken> writeInfoLists, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Dictionary<string, Tuple<bool, string>> results = new();
|
||||
try
|
||||
{
|
||||
WriteValue valueToWrite = new()
|
||||
WriteValueCollection valuesToWrite = new();
|
||||
foreach (var item in writeInfoLists)
|
||||
{
|
||||
NodeId = new NodeId(tag),
|
||||
AttributeId = Attributes.Value,
|
||||
};
|
||||
var node = (VariableNode)ReadNode(tag.ToString(), false);
|
||||
var dataValue = JsonUtils.Decode(
|
||||
m_session.MessageContext,
|
||||
node.DataType,
|
||||
TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable),
|
||||
value.CalculateActualValueRank(),
|
||||
value
|
||||
);
|
||||
valueToWrite.Value = dataValue;
|
||||
WriteValueCollection valuesToWrite = new()
|
||||
{
|
||||
valueToWrite
|
||||
};
|
||||
WriteValue valueToWrite = new()
|
||||
{
|
||||
NodeId = new NodeId(item.Key),
|
||||
AttributeId = Attributes.Value,
|
||||
};
|
||||
var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken);
|
||||
var dataValue = JsonUtils.Decode(
|
||||
m_session.MessageContext,
|
||||
variableNode.DataType,
|
||||
TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable),
|
||||
item.Value.CalculateActualValueRank(),
|
||||
item.Value
|
||||
);
|
||||
valueToWrite.Value = dataValue;
|
||||
|
||||
valuesToWrite.Add(valueToWrite);
|
||||
}
|
||||
|
||||
var result = await m_session.WriteAsync(
|
||||
requestHeader: null,
|
||||
nodesToWrite: valuesToWrite, token);
|
||||
nodesToWrite: valuesToWrite, cancellationToken);
|
||||
|
||||
ClientBase.ValidateResponse(result.Results, valuesToWrite);
|
||||
ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, valuesToWrite);
|
||||
if (!StatusCode.IsGood(result.Results[0]))
|
||||
|
||||
var keys = writeInfoLists.Keys.ToList();
|
||||
for (int i = 0; i < keys.Count; i++)
|
||||
{
|
||||
return new OperResult(result.Results[0].ToString());
|
||||
if (!StatusCode.IsGood(result.Results[i]))
|
||||
results.Add(keys[i], Tuple.Create(true, result.Results[i].ToString()));
|
||||
else
|
||||
{
|
||||
results.Add(keys[i], Tuple.Create(false, "成功"));
|
||||
}
|
||||
}
|
||||
return OperResult.CreateSuccessResult();
|
||||
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
var keys = writeInfoLists.Keys.ToList();
|
||||
foreach (var item in keys)
|
||||
{
|
||||
results.Add(item, Tuple.Create(true, ex.Message));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器读取值
|
||||
/// </summary>
|
||||
private async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(NodeId[] nodeIds, CancellationToken token = default)
|
||||
private async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(NodeId[] nodeIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (m_session == null)
|
||||
{
|
||||
@@ -565,7 +710,7 @@ public class OPCUAClient : DisposableObject
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
nodesToRead,
|
||||
token);
|
||||
cancellationToken);
|
||||
var results = result.Results;
|
||||
var diagnosticInfos = result.DiagnosticInfos;
|
||||
ClientBase.ValidateResponse(results, nodesToRead);
|
||||
@@ -573,10 +718,10 @@ public class OPCUAClient : DisposableObject
|
||||
List<(string, DataValue, JToken)> jTokens = new();
|
||||
for (int i = 0; i < results.Count; i++)
|
||||
{
|
||||
var node = (VariableNode)ReadNode(nodeIds[i].ToString(), false);
|
||||
var type = TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable);
|
||||
var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken);
|
||||
var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable);
|
||||
var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
|
||||
jTokens.Add((node.NodeId.ToString(), results[i], jToken));
|
||||
jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
|
||||
}
|
||||
return jTokens.ToList();
|
||||
}
|
||||
@@ -584,7 +729,7 @@ public class OPCUAClient : DisposableObject
|
||||
/// <summary>
|
||||
/// 从服务器或缓存读取节点
|
||||
/// </summary>
|
||||
private Node ReadNode(string nodeIdStr, bool isOnlyServer = true)
|
||||
private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!isOnlyServer)
|
||||
{
|
||||
@@ -593,21 +738,68 @@ public class OPCUAClient : DisposableObject
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
NodeId nodeToRead = new(nodeIdStr);
|
||||
var node = m_session.ReadNode(nodeToRead);
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, (VariableNode)node);
|
||||
var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
|
||||
if (OPCNode.LoadType)
|
||||
await typeSystem.LoadType(node.DataType);
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, node);
|
||||
return node;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器或缓存读取节点
|
||||
/// </summary>
|
||||
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
|
||||
{
|
||||
if (!isOnlyServer)
|
||||
{
|
||||
if (_variableDicts.TryGetValue(nodeIdStr, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
NodeId nodeToRead = new(nodeIdStr);
|
||||
var node = (VariableNode)m_session.ReadNode(nodeToRead, NodeClass.Variable, false);
|
||||
_variableDicts.AddOrUpdate(nodeIdStr, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器读取节点
|
||||
/// </summary>
|
||||
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
|
||||
{
|
||||
List<NodeId> nodeIds = new List<NodeId>();
|
||||
foreach (var item in nodeIdStrs)
|
||||
{
|
||||
NodeId nodeToRead = new(item);
|
||||
nodeIds.Add(nodeToRead);
|
||||
}
|
||||
(IList<Node>, IList<ServiceResult>) nodes = await m_session.ReadNodesAsync(nodeIds, NodeClass.Variable, false, cancellationToken);
|
||||
for (int i = 0; i < nodes.Item1.Count; i++)
|
||||
{
|
||||
if (StatusCode.IsGood(nodes.Item2[i].StatusCode))
|
||||
{
|
||||
var node = ((VariableNode)nodes.Item1[i]);
|
||||
await typeSystem.LoadType(node.DataType);
|
||||
_variableDicts.AddOrUpdate(nodeIdStrs[i], node);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus(3, DateTime.Now, $"获取服务器节点信息失败{nodes.Item2[i]}");
|
||||
}
|
||||
}
|
||||
return nodes.Item1.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 特性
|
||||
|
||||
/// <summary>
|
||||
/// 读取一个节点的所有属性
|
||||
/// </summary>
|
||||
public async Task<OperResult<List<OPCNodeAttribute>>> ReadNoteAttributeAsync(string tag, uint attributesId, CancellationToken token = default)
|
||||
public async Task<List<OPCNodeAttribute>> ReadNoteAttributeAsync(string tag, uint attributesId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
BrowseDescriptionCollection nodesToBrowse = new();
|
||||
ReadValueIdCollection nodesToRead = new();
|
||||
@@ -630,16 +822,16 @@ public class OPCUAClient : DisposableObject
|
||||
};
|
||||
nodesToBrowse.Add(nodeToBrowse);
|
||||
|
||||
var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, token);
|
||||
var result2 = result1.Copy<List<OPCNodeAttribute>>();
|
||||
result2.Content = result1.Content?.Values?.FirstOrDefault()?.ToList();
|
||||
var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken);
|
||||
|
||||
var result2 = result1.Values.FirstOrDefault();
|
||||
return result2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取节点的所有属性
|
||||
/// </summary>
|
||||
public async Task<OperResult<Dictionary<string, List<OPCNodeAttribute>>>> ReadNoteAttributeAsync(List<string> tags, CancellationToken token)
|
||||
public async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(List<string> tags, CancellationToken cancellationToken)
|
||||
{
|
||||
BrowseDescriptionCollection nodesToBrowse = new();
|
||||
ReadValueIdCollection nodesToRead = new();
|
||||
@@ -666,10 +858,9 @@ public class OPCUAClient : DisposableObject
|
||||
ResultMask = (uint)BrowseResultMask.All
|
||||
};
|
||||
nodesToBrowse.Add(nodeToBrowse);
|
||||
|
||||
}
|
||||
|
||||
return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, token);
|
||||
return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -829,16 +1020,13 @@ public class OPCUAClient : DisposableObject
|
||||
|
||||
return nodeAttribute.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
public void Dispose()
|
||||
{
|
||||
Disconnect();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region 私有方法
|
||||
@@ -853,64 +1041,15 @@ public class OPCUAClient : DisposableObject
|
||||
throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new session.
|
||||
/// </summary>
|
||||
/// <returns>The new session object.</returns>
|
||||
private async Task<ISession> ConnectAsync(string serverUrl)
|
||||
{
|
||||
Disconnect();
|
||||
|
||||
if (m_configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException("未初始化配置");
|
||||
}
|
||||
var useSecurity = OPCNode?.IsUseSecurity ?? true;
|
||||
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, useSecurity);
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
|
||||
UserIdentity userIdentity;
|
||||
ConfiguredEndpoint endpoint = new(null, endpointDescription, endpointConfiguration);
|
||||
if (!OPCNode.UserName.IsNullOrEmpty())
|
||||
{
|
||||
userIdentity = new UserIdentity(OPCNode.UserName, OPCNode.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
userIdentity = new UserIdentity(new AnonymousIdentityToken());
|
||||
}
|
||||
//创建本地证书
|
||||
await m_application.CheckApplicationInstanceCertificate(true, 0, 1200);
|
||||
m_session = await Opc.Ua.Client.Session.Create(
|
||||
m_configuration,
|
||||
endpoint,
|
||||
false,
|
||||
false,
|
||||
(string.IsNullOrEmpty(OPCUAName)) ? m_configuration.ApplicationName : OPCUAName,
|
||||
60000,
|
||||
userIdentity,
|
||||
Array.Empty<string>());
|
||||
|
||||
Log.Debug("连接成功");
|
||||
|
||||
m_session.KeepAliveInterval = OPCNode.KeepAliveInterval == 0 ? 60000 : OPCNode.KeepAliveInterval;
|
||||
m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
|
||||
|
||||
//如果是订阅模式,连接时添加订阅组
|
||||
if (OPCNode.ActiveSubscribe)
|
||||
AddSubscription(Guid.NewGuid().ToString(), Variables.ToArray());
|
||||
return m_session;
|
||||
}
|
||||
|
||||
|
||||
private async Task<OperResult<Dictionary<string, List<OPCNodeAttribute>>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken token)
|
||||
private async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken cancellationToken)
|
||||
{
|
||||
int startOfProperties = nodesToRead.Count;
|
||||
|
||||
ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(m_session, nodesToBrowse, false, token);
|
||||
ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(m_session, nodesToBrowse, false, cancellationToken);
|
||||
|
||||
if (references == null)
|
||||
{
|
||||
return new OperResult<Dictionary<string, List<OPCNodeAttribute>>>("浏览失败");
|
||||
throw new("浏览失败");
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < references.Count; ii++)
|
||||
@@ -932,7 +1071,7 @@ public class OPCUAClient : DisposableObject
|
||||
null,
|
||||
0,
|
||||
TimestampsToReturn.Neither,
|
||||
nodesToRead, token);
|
||||
nodesToRead, cancellationToken);
|
||||
|
||||
ClientBase.ValidateResponse(result.Results, nodesToRead);
|
||||
ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, nodesToRead);
|
||||
@@ -978,7 +1117,6 @@ public class OPCUAClient : DisposableObject
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (nodeAttributes.ContainsKey(nodeToRead.NodeId.ToString()))
|
||||
{
|
||||
nodeAttributes[nodeToRead.NodeId.ToString()].Add(item);
|
||||
@@ -988,8 +1126,7 @@ public class OPCUAClient : DisposableObject
|
||||
nodeAttributes.Add(nodeToRead.NodeId.ToString(), new() { item });
|
||||
}
|
||||
}
|
||||
|
||||
return OperResult.CreateSuccessResult(nodeAttributes);
|
||||
return nodeAttributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -997,22 +1134,46 @@ public class OPCUAClient : DisposableObject
|
||||
/// </summary>
|
||||
private void Server_ReconnectComplete(object sender, EventArgs e)
|
||||
{
|
||||
if (!Object.ReferenceEquals(sender, m_reConnectHandler))
|
||||
try
|
||||
{
|
||||
return;
|
||||
if (!Object.ReferenceEquals(sender, m_reConnectHandler))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_session = m_reConnectHandler.Session;
|
||||
m_reConnectHandler.Dispose();
|
||||
m_reConnectHandler = null;
|
||||
|
||||
// raise any additional notifications.
|
||||
m_ReconnectComplete?.Invoke(this, e);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
m_session = m_reConnectHandler.Session;
|
||||
m_reConnectHandler.SafeDispose();
|
||||
m_reConnectHandler = null;
|
||||
|
||||
/// <summary>
|
||||
/// Report the client status
|
||||
/// </summary>
|
||||
/// <param name="logLevel">Whether the status represents an error. </param>
|
||||
/// <param name="time">The time associated with the status.</param>
|
||||
/// <param name="status">The status message.</param>
|
||||
/// <param name="args">Arguments used to format the status message.</param>
|
||||
private void UpdateStatus(int logLevel, DateTime time, string status, params object[] args)
|
||||
{
|
||||
m_OpcStatusChange?.Invoke(this, new OpcUaStatusEventArgs()
|
||||
{
|
||||
LogLevel = logLevel,
|
||||
Time = time.ToLocalTime(),
|
||||
Text = String.Format(status, args),
|
||||
});
|
||||
}
|
||||
|
||||
private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
|
||||
{
|
||||
if (checkLock.IsWaitting) { return; }
|
||||
checkLock.Wait();
|
||||
try
|
||||
lock (checkLock)
|
||||
{
|
||||
if (!Object.ReferenceEquals(session, m_session))
|
||||
{
|
||||
@@ -1021,26 +1182,39 @@ public class OPCUAClient : DisposableObject
|
||||
|
||||
if (ServiceResult.IsBad(e.Status))
|
||||
{
|
||||
Log.Warning($"心跳检测错误:{e.Status}");
|
||||
if (m_session.KeepAliveInterval <= 0)
|
||||
{
|
||||
UpdateStatus(3, e.CurrentTime, "Communication Error ({0})", e.Status);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateStatus(3, e.CurrentTime, "Reconnecting in {0}s", m_session.KeepAliveInterval / 1000);
|
||||
|
||||
if (m_reConnectHandler == null)
|
||||
{
|
||||
m_ReconnectStarting?.Invoke(this, e);
|
||||
|
||||
m_reConnectHandler = new SessionReconnectHandler();
|
||||
m_reConnectHandler.BeginReconnect(m_session, m_session.KeepAliveInterval, Server_ReconnectComplete);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Trace($"心跳检测正常 [{session.Endpoint.EndpointUrl}]");
|
||||
}
|
||||
finally
|
||||
{
|
||||
checkLock.Release();
|
||||
// update status.
|
||||
UpdateStatus(0, e.CurrentTime, "Session_KeepAlive Connected [{0}]", session.Endpoint.EndpointUrl);
|
||||
|
||||
// raise any additional notifications.
|
||||
m_KeepAliveComplete?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Raises the connect complete event on the main GUI thread.
|
||||
/// </summary>
|
||||
private void DoConnectComplete(bool state)
|
||||
{
|
||||
m_ConnectComplete?.Invoke(this, state);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
#region copyright
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#endregion
|
||||
|
||||
using Opc.Ua;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA的状态更新消息
|
||||
/// </summary>
|
||||
public class OpcUaStatusEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志等级,<br></br>
|
||||
/// 更为详细的步骤型日志输出 Trace = 0,<br></br>
|
||||
/// 调试信息日志Debug = 1,<br></br>
|
||||
/// 消息类日志输出 Info = 2,<br></br>
|
||||
/// 警告类日志输出 Warning = 3,<br></br>
|
||||
/// 错误类日志输出 Error = 4,<br></br>
|
||||
/// 不可控中断类日输出Critical = 5,
|
||||
/// </summary>
|
||||
public int LogLevel { get; set; }
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public DateTime Time { get; set; }
|
||||
/// <summary>
|
||||
/// 文本
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取属性过程中用于描述的
|
||||
/// </summary>
|
||||
public class OPCNodeAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性的名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作结果状态描述
|
||||
/// </summary>
|
||||
public StatusCode StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 属性的类型描述
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 属性的值,如果读取错误,返回文本描述
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0;net7.0</TargetFrameworks>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.4.372.56" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.4.372.56" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
@@ -10,10 +10,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
|
||||
|
||||
internal static class CollectionExtensions
|
||||
@@ -24,7 +20,7 @@ internal static class CollectionExtensions
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="this"></param>
|
||||
/// <param name="where"></param>
|
||||
public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
|
||||
internal static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
|
||||
{
|
||||
foreach (var obj in @this.Where(where).ToList())
|
||||
{
|
||||
@@ -39,7 +35,7 @@ internal static class CollectionExtensions
|
||||
/// <param name="source"></param>
|
||||
/// <param name="selector"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<TResult[]> SelectAsync<T, TResult>(this IEnumerable<T> source, Func<T, Task<TResult>> selector)
|
||||
internal static Task<TResult[]> SelectAsync<T, TResult>(this IEnumerable<T> source, Func<T, Task<TResult>> selector)
|
||||
{
|
||||
return Task.WhenAll(source.Select(selector));
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
#region copyright
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://diego2098.gitee.io/thingsgateway-docs/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
#endregion
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
|
||||
|
||||
/// <summary>
|
||||
/// DictionaryExtension
|
||||
/// </summary>
|
||||
internal static class DictionaryExtension
|
||||
{
|
||||
#region 字典扩展
|
||||
|
||||
/// <summary>
|
||||
/// 移除满足条件的项目。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="pairs"></param>
|
||||
/// <param name="func"></param>
|
||||
/// <returns></returns>
|
||||
internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
|
||||
{
|
||||
var list = new List<TKey>();
|
||||
foreach (var item in pairs)
|
||||
{
|
||||
if (func?.Invoke(item) == true)
|
||||
{
|
||||
list.Add(item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (pairs.TryRemove(item, out _))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
dictionary.Add(tkey, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 尝试添加
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
|
||||
{
|
||||
if (dictionary.ContainsKey(tkey))
|
||||
{
|
||||
dictionary[tkey] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(tkey, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值。如果键不存在,则返回默认值。
|
||||
/// </summary>
|
||||
/// <typeparam name="Tkey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="tkey"></param>
|
||||
/// <returns></returns>
|
||||
internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
|
||||
{
|
||||
return dictionary.TryGetValue(tkey, out var value) ? value : default;
|
||||
}
|
||||
#endregion 字典扩展
|
||||
}
|
@@ -13,10 +13,7 @@
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
|
||||
/// <summary>
|
||||
@@ -150,16 +147,17 @@ public class FormUtils
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 浏览地址空间
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="nodesToBrowse"></param>
|
||||
/// <param name="throwOnError"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ServiceResultException"></exception>
|
||||
public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError, CancellationToken token = default)
|
||||
public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -174,16 +172,23 @@ public class FormUtils
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
nodesToBrowse, token);
|
||||
nodesToBrowse, cancellationToken);
|
||||
var results = result.Results;
|
||||
var diagnosticInfos = result.DiagnosticInfos;
|
||||
ClientBase.ValidateResponse(results, nodesToBrowse);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);
|
||||
|
||||
ByteStringCollection continuationPoints = new();
|
||||
var continuationPoints = PrepareBrowseNext(result.Results);
|
||||
|
||||
for (int ii = 0; ii < nodesToBrowse.Count; ii++)
|
||||
{
|
||||
|
||||
// check if all references have been fetched.
|
||||
if (results[ii].References.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for error.
|
||||
if (StatusCode.IsBad(results[ii].StatusCode))
|
||||
{
|
||||
@@ -198,33 +203,22 @@ public class FormUtils
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if all references have been fetched.
|
||||
if (results[ii].References.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// save results.
|
||||
references.AddRange(results[ii].References);
|
||||
|
||||
// check for continuation point.
|
||||
if (results[ii].ContinuationPoint != null)
|
||||
{
|
||||
continuationPoints.Add(results[ii].ContinuationPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// process continuation points.
|
||||
ByteStringCollection revisedContiuationPoints = new();
|
||||
|
||||
while (continuationPoints.Count > 0)
|
||||
while (continuationPoints.Any())
|
||||
{
|
||||
// continue browse operation.
|
||||
var nextResult = await session.BrowseNextAsync(
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
continuationPoints
|
||||
, token);
|
||||
, cancellationToken);
|
||||
results = nextResult.Results;
|
||||
diagnosticInfos = nextResult.DiagnosticInfos;
|
||||
ClientBase.ValidateResponse(results, continuationPoints);
|
||||
@@ -232,6 +226,11 @@ public class FormUtils
|
||||
|
||||
for (int ii = 0; ii < continuationPoints.Count; ii++)
|
||||
{
|
||||
// check if all references have been fetched.
|
||||
if (results[ii].References.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for error.
|
||||
if (StatusCode.IsBad(results[ii].StatusCode))
|
||||
@@ -239,24 +238,16 @@ public class FormUtils
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if all references have been fetched.
|
||||
if (results[ii].References.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// save results.
|
||||
references.AddRange(results[ii].References);
|
||||
|
||||
// check for continuation point.
|
||||
if (results[ii].ContinuationPoint != null)
|
||||
{
|
||||
revisedContiuationPoints.Add(results[ii].ContinuationPoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if browsing must continue;
|
||||
revisedContiuationPoints = continuationPoints;
|
||||
continuationPoints = PrepareBrowseNext(nextResult.Results);
|
||||
}
|
||||
|
||||
// check if unprocessed results exist.
|
||||
@@ -283,10 +274,10 @@ public class FormUtils
|
||||
/// <param name="session"></param>
|
||||
/// <param name="nodeToBrowse"></param>
|
||||
/// <param name="throwOnError"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ServiceResultException"></exception>
|
||||
public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescription nodeToBrowse, bool throwOnError, CancellationToken token = default)
|
||||
public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescription nodeToBrowse, bool throwOnError, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -305,7 +296,7 @@ public class FormUtils
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
nodesToBrowse, token);
|
||||
nodesToBrowse, cancellationToken);
|
||||
var results = result.Results;
|
||||
var diagnosticInfos = result.DiagnosticInfos;
|
||||
ClientBase.ValidateResponse(results, nodesToBrowse);
|
||||
@@ -340,7 +331,7 @@ public class FormUtils
|
||||
var nextResult = await session.BrowseNextAsync(
|
||||
null,
|
||||
false,
|
||||
continuationPoints, token);
|
||||
continuationPoints, cancellationToken);
|
||||
results = nextResult.Results;
|
||||
diagnosticInfos = nextResult.DiagnosticInfos;
|
||||
ClientBase.ValidateResponse(results, continuationPoints);
|
||||
@@ -813,7 +804,7 @@ public class FormUtils
|
||||
public static async Task<List<NodeId>> TranslateBrowsePaths(
|
||||
ISession session,
|
||||
NodeId startNodeId,
|
||||
NamespaceTable namespacesUris, CancellationToken token,
|
||||
NamespaceTable namespacesUris, CancellationToken cancellationToken,
|
||||
params string[] relativePaths)
|
||||
{
|
||||
// build the list of browse paths to follow by parsing the relative paths.
|
||||
@@ -844,7 +835,7 @@ public class FormUtils
|
||||
var result = await session.TranslateBrowsePathsToNodeIdsAsync(
|
||||
null,
|
||||
browsePaths,
|
||||
token);
|
||||
cancellationToken);
|
||||
BrowsePathResultCollection results = result.Results;
|
||||
DiagnosticInfoCollection diagnosticInfos = result.DiagnosticInfos;
|
||||
// ensure that the server returned valid results.
|
||||
@@ -1115,4 +1106,23 @@ public class FormUtils
|
||||
_ => valueRank.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the continuation point collection from the browse result
|
||||
/// collection for the BrowseNext service.
|
||||
/// </summary>
|
||||
/// <param name="browseResultCollection">The browse result collection to use.</param>
|
||||
/// <returns>The collection of continuation points for the BrowseNext service.</returns>
|
||||
private static ByteStringCollection PrepareBrowseNext(BrowseResultCollection browseResultCollection)
|
||||
{
|
||||
var continuationPoints = new ByteStringCollection();
|
||||
foreach (var browseResult in browseResultCollection)
|
||||
{
|
||||
if (browseResult.ContinuationPoint != null)
|
||||
{
|
||||
continuationPoints.Add(browseResult.ContinuationPoint);
|
||||
}
|
||||
}
|
||||
return continuationPoints;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user