Compare commits
	
		
			153 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 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 | 
| @@ -7,7 +7,7 @@ | |||||||
|  |  | ||||||
|  **ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) |  **ThingsGateway** 存储库同时提供 [**设备采集驱动**](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22) | ||||||
|  |  | ||||||
|  **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 [**ThingsGateway.Admin**](https://gitee.com/dotnetchina/ThingsGateway/blob/master/framework/ThingsGateway.Admin.sln) |  **ThingsGateway** 存储库同时提供 **基于Blazor的权限框架** 查看 **ThingsGateway - Admin** | ||||||
|  |  | ||||||
|  |  | ||||||
| ## 文档 | ## 文档 | ||||||
|   | |||||||
| @@ -1,149 +1,7 @@ | |||||||
| [*.cs] | [*.cs] | ||||||
|  |  | ||||||
| # CA1822: 将成员标记为 static | # CA1848: 使用 LoggerMessage 委托 | ||||||
| dotnet_diagnostic.CA1822.severity = none | dotnet_diagnostic.CA1848.severity = none | ||||||
|  |  | ||||||
| # CA1816: Dispose 方法应调用 SuppressFinalize |  | ||||||
| dotnet_diagnostic.CA1816.severity = none |  | ||||||
|  |  | ||||||
| # CA2254: 模板应为静态表达式 | # CA2254: 模板应为静态表达式 | ||||||
| dotnet_diagnostic.CA2254.severity = none | dotnet_diagnostic.CA2254.severity = suggestion | ||||||
|  |  | ||||||
| [*.cs] |  | ||||||
| #### 命名样式 #### |  | ||||||
|  |  | ||||||
| # 命名规则 |  | ||||||
|  |  | ||||||
| dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion |  | ||||||
| dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface |  | ||||||
| dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i |  | ||||||
|  |  | ||||||
| dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion |  | ||||||
| dotnet_naming_rule.types_should_be_pascal_case.symbols = types |  | ||||||
| dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case |  | ||||||
|  |  | ||||||
| dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion |  | ||||||
| dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members |  | ||||||
| dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case |  | ||||||
|  |  | ||||||
| # 符号规范 |  | ||||||
|  |  | ||||||
| dotnet_naming_symbols.interface.applicable_kinds = interface |  | ||||||
| dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |  | ||||||
| dotnet_naming_symbols.interface.required_modifiers =  |  | ||||||
|  |  | ||||||
| dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum |  | ||||||
| dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |  | ||||||
| dotnet_naming_symbols.types.required_modifiers =  |  | ||||||
|  |  | ||||||
| dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method |  | ||||||
| dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected |  | ||||||
| dotnet_naming_symbols.non_field_members.required_modifiers =  |  | ||||||
|  |  | ||||||
| # 命名样式 |  | ||||||
|  |  | ||||||
| dotnet_naming_style.begins_with_i.required_prefix = I |  | ||||||
| dotnet_naming_style.begins_with_i.required_suffix =  |  | ||||||
| dotnet_naming_style.begins_with_i.word_separator =  |  | ||||||
| dotnet_naming_style.begins_with_i.capitalization = pascal_case |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
|  |  | ||||||
| # IDE0056: 使用索引运算符 |  | ||||||
| dotnet_diagnostic.IDE0056.severity = none |  | ||||||
|  |  | ||||||
| # CA1830: 最好使用 StringBuilder 的强类型 Append 和 Insert 方法重载 |  | ||||||
| dotnet_diagnostic.CA1830.severity = none |  | ||||||
|  |  | ||||||
| # CA1847: 将字符型文本用于单个字符查找 |  | ||||||
| dotnet_diagnostic.CA1847.severity = none |  | ||||||
|  |  | ||||||
| [*.vb] |  | ||||||
| #### 命名样式 #### |  | ||||||
|  |  | ||||||
| # 命名规则 |  | ||||||
|  |  | ||||||
| dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion |  | ||||||
| dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface |  | ||||||
| dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 |  | ||||||
|  |  | ||||||
| dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion |  | ||||||
| dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 |  | ||||||
| dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 |  | ||||||
|  |  | ||||||
| dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion |  | ||||||
| dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 |  | ||||||
| dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 |  | ||||||
|  |  | ||||||
| # 符号规范 |  | ||||||
|  |  | ||||||
| dotnet_naming_symbols.interface.applicable_kinds = interface |  | ||||||
| dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected |  | ||||||
| dotnet_naming_symbols.interface.required_modifiers =  |  | ||||||
|  |  | ||||||
| dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum |  | ||||||
| dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected |  | ||||||
| dotnet_naming_symbols.类型.required_modifiers =  |  | ||||||
|  |  | ||||||
| dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method |  | ||||||
| dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected |  | ||||||
| dotnet_naming_symbols.非字段成员.required_modifiers =  |  | ||||||
|  |  | ||||||
| # 命名样式 |  | ||||||
|  |  | ||||||
| dotnet_naming_style.以_i_开始.required_prefix = I |  | ||||||
| dotnet_naming_style.以_i_开始.required_suffix =  |  | ||||||
| dotnet_naming_style.以_i_开始.word_separator =  |  | ||||||
| dotnet_naming_style.以_i_开始.capitalization = pascal_case |  | ||||||
|  |  | ||||||
| dotnet_naming_style.帕斯卡拼写法.required_prefix =  |  | ||||||
| dotnet_naming_style.帕斯卡拼写法.required_suffix =  |  | ||||||
| dotnet_naming_style.帕斯卡拼写法.word_separator =  |  | ||||||
| dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case |  | ||||||
|  |  | ||||||
| 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.13</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,3 +12,5 @@ | |||||||
| 
 | 
 | ||||||
| global using System; | global using System; | ||||||
| 
 | 
 | ||||||
|  | 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.Message}")); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <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.Message}")); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <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.Message)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |             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> | ||||||
| @@ -10,14 +10,10 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Serial; | namespace ThingsGateway.Foundation.Demo; | ||||||
| 
 |  | ||||||
| using TouchSocket.Core; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Blazor; |  | ||||||
| 
 | 
 | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| public partial class SerialClientPage | public partial class SerialSessionPage : IDisposable | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 日志输出 |     /// 日志输出 | ||||||
| @@ -28,35 +24,29 @@ public partial class SerialClientPage | |||||||
| 
 | 
 | ||||||
|     private readonly SerialProperty serialProperty = new(); |     private readonly SerialProperty serialProperty = new(); | ||||||
| 
 | 
 | ||||||
|     private SerialClient SerialClient { get; set; } = new(); |     private SerialSession SerialSession { get; set; } = new(); | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public void Dispose() |  | ||||||
|     { |  | ||||||
|         SerialClient.SafeDispose(); |  | ||||||
|     } |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取对象 |     /// 获取对象 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public SerialClient GetSerialClient() |     public SerialSession GetSerialSession() | ||||||
|     { |     { | ||||||
|         config?.Dispose(); |         config ??= new TouchSocketConfig(); | ||||||
|         config = new TouchSocketConfig(); |         var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace }; | ||||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; |         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace }); | ||||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); |  | ||||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); |         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||||
|         config.SetSerialProperty(serialProperty); |         config.SetSerialProperty(serialProperty); | ||||||
|         //载入配置 |         //载入配置 | ||||||
|         SerialClient.Setup(config); |         SerialSession.Setup(config); | ||||||
|         return SerialClient; |         return SerialSession; | ||||||
|     } |     } | ||||||
|     private async Task ConnectAsync() |     private async Task ConnectAsync() | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             SerialClient.Close(); |             SerialSession.Close(); | ||||||
|             await GetSerialClient().ConnectAsync(); |             await GetSerialSession().ConnectAsync(); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -68,25 +58,49 @@ public partial class SerialClientPage | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             SerialClient.Close(); |             SerialSession.Close(); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             LogAction?.Invoke(LogLevel.Error, null, null, ex); |             LogAction?.Invoke(LogLevel.Error, null, null, ex); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override void OnInitialized() |     protected override void OnInitialized() | ||||||
|     { |     { | ||||||
|         config ??= new TouchSocketConfig(); |         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)); |  | ||||||
|         SerialClient = new SerialClient(); |  | ||||||
|         SerialClient.Setup(config); |  | ||||||
|         base.OnInitialized(); |         base.OnInitialized(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); |  | ||||||
| 
 | 
 | ||||||
|  |     /// <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 BlazorComponent; | ||||||
| @using Microsoft.AspNetCore.Components.Web; | @using Microsoft.AspNetCore.Components.Web; | ||||||
| @using System.IO.Ports; | @using System.IO.Ports; | ||||||
| @using System.Collections.Concurrent; | @using System.Collections.Concurrent; | ||||||
| @using ThingsGateway.Foundation; | @using ThingsGateway.Foundation.Core; | ||||||
| @using ThingsGateway.Foundation.Serial; |  | ||||||
| @using Masa.Blazor | @using Masa.Blazor | ||||||
| @using TouchSocket.Core; | <MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%"> | ||||||
| @using TouchSocket.Sockets; |     <div class="mb-4">通道配置</div> | ||||||
| @implements IDisposable |     <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center"> | ||||||
| <MCard Class="pa-4" Flat Elevation="0" Rounded="false"> | 
 | ||||||
|     <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start"> |         <MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP /> | ||||||
|         <MTextField Class="ma-1" 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 /> | ||||||
|         <MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port /> |  | ||||||
| 
 | 
 | ||||||
|         <MButton Class="ma-1" OnClick=@ConnectAsync Color="primary"> |         <MButton Class="ma-1" OnClick=@ConnectAsync Color="primary"> | ||||||
|             连接 |             连接 | ||||||
| @@ -33,7 +31,4 @@ | |||||||
|             断开 |             断开 | ||||||
|         </MButton> |         </MButton> | ||||||
|     </MRow> |     </MRow> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </MCard> | </MCard> | ||||||
| @@ -10,17 +10,10 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using Microsoft.AspNetCore.Components; | namespace ThingsGateway.Foundation.Demo; | ||||||
| 
 |  | ||||||
| using ThingsGateway.Foundation; |  | ||||||
| 
 |  | ||||||
| using TouchSocket.Core; |  | ||||||
| using TouchSocket.Sockets; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Blazor; |  | ||||||
| 
 | 
 | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| public partial class TcpClientPage | public partial class TcpClientPage : IDisposable | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 日志输出 |     /// 日志输出 | ||||||
| @@ -33,24 +26,17 @@ public partial class TcpClientPage | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     private string IP = "127.0.0.1"; |     private string IP = "127.0.0.1"; | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 端口 |     /// Port | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     [Parameter] |  | ||||||
|     public int Port { get; set; } = 502; |     public int Port { get; set; } = 502; | ||||||
| 
 | 
 | ||||||
|     private TcpClientEx TcpClientEx { get; set; } = new(); |     private TcpClient TcpClient { get; set; } = new(); | ||||||
| 
 |  | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public void Dispose() |  | ||||||
|     { |  | ||||||
|         TcpClientEx.SafeDispose(); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     private async Task ConnectAsync() |     private async Task ConnectAsync() | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             TcpClientEx.Close(); |             TcpClient.Close(); | ||||||
|             await GetTcpClient().ConnectAsync(); |             await GetTcpClient().ConnectAsync(); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
| @@ -64,7 +50,7 @@ public partial class TcpClientPage | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             TcpClientEx.Close(); |             TcpClient.Close(); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -76,32 +62,55 @@ public partial class TcpClientPage | |||||||
|     /// 获取对象 |     /// 获取对象 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public TcpClientEx GetTcpClient() |     public TcpClient GetTcpClient() | ||||||
|     { |     { | ||||||
|         config ??= new TouchSocketConfig(); |         config ??= new TouchSocketConfig(); | ||||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; |         var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace }; | ||||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); |         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace }); | ||||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); |         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||||
|         config.SetRemoteIPHost(new IPHost(IP + ":" + Port)).SetBufferLength(300); |         config.SetRemoteIPHost(new IPHost(IP + ":" + Port)); | ||||||
|         //载入配置 |         //载入配置 | ||||||
|         TcpClientEx.Setup(config); |         TcpClient.Setup(config); | ||||||
|         return TcpClientEx; |         return TcpClient; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override void OnInitialized() |     protected override void OnInitialized() | ||||||
|     { |     { | ||||||
|         config?.Dispose(); |         config ??= new TouchSocketConfig(); | ||||||
|         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.SetRemoteIPHost(new IPHost(IP + ":" + Port)).SetBufferLength(300); |  | ||||||
|         TcpClientEx = new TcpClientEx(); |  | ||||||
|         TcpClientEx.Setup(config); |  | ||||||
|         base.OnInitialized(); |         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); |     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 BlazorComponent; | ||||||
| @using Microsoft.AspNetCore.Components.Web; | @using Microsoft.AspNetCore.Components.Web; | ||||||
| @using System.IO.Ports; | @using System.IO.Ports; | ||||||
| @using System.Collections.Concurrent; | @using System.Collections.Concurrent; | ||||||
| @using ThingsGateway.Foundation; | @using ThingsGateway.Foundation.Core; | ||||||
| @using ThingsGateway.Foundation.Serial; |  | ||||||
| @using Masa.Blazor | @using Masa.Blazor | ||||||
| @using TouchSocket.Core; | 
 | ||||||
| @using TouchSocket.Sockets; | <MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%"> | ||||||
| @implements IDisposable |     <div class="mb-4">通道配置</div> | ||||||
| <MCard Class="pa-4" Flat Elevation="0" Rounded="false"> |     <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center"> | ||||||
|     <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start"> | 
 | ||||||
|         <MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip /> |         <MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip /> | ||||||
|         <MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port /> |         <MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port /> | ||||||
| 
 | 
 | ||||||
|         <MButton Class="ma-1" OnClick=@Connect Color="primary"> |         <MButton Class="ma-1" OnClick=@Connect Color="primary"> | ||||||
|             连接 |             连接 | ||||||
| @@ -32,8 +31,8 @@ | |||||||
|         <MButton Class="ma-1" OnClick=@DisConnect Color="red"> |         <MButton Class="ma-1" OnClick=@DisConnect Color="red"> | ||||||
|             断开 |             断开 | ||||||
|         </MButton> |         </MButton> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     </MRow> |     </MRow> | ||||||
| 
 | </MCard> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </MCard> |  | ||||||
| @@ -10,13 +10,10 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using TouchSocket.Core; | namespace ThingsGateway.Foundation.Demo; | ||||||
| using TouchSocket.Sockets; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Blazor; |  | ||||||
| 
 | 
 | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| public partial class TcpServerPage | public partial class TcpServerPage : IDisposable | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 日志输出 |     /// 日志输出 | ||||||
| @@ -31,11 +28,6 @@ public partial class TcpServerPage | |||||||
| 
 | 
 | ||||||
|     private TcpService TcpServer { get; set; } = new(); |     private TcpService TcpServer { get; set; } = new(); | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public void Dispose() |  | ||||||
|     { |  | ||||||
|         TcpServer.SafeDispose(); |  | ||||||
|     } |  | ||||||
|     private void Connect() |     private void Connect() | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
| @@ -68,32 +60,49 @@ public partial class TcpServerPage | |||||||
|     public TcpService GetTcpServer() |     public TcpService GetTcpServer() | ||||||
|     { |     { | ||||||
|         config ??= new TouchSocketConfig(); |         config ??= new TouchSocketConfig(); | ||||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; |         var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace }; | ||||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); |         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace }); | ||||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); |         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||||
|         config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) }); |         config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) }); | ||||||
|         config.SetBufferLength(300); |  | ||||||
|         //载入配置 |         //载入配置 | ||||||
|         TcpServer.Setup(config); |         TcpServer.Setup(config); | ||||||
|         return TcpServer; |         return TcpServer; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     protected override void OnInitialized() | ||||||
|  |     { | ||||||
|  |         config ??= new TouchSocketConfig(); | ||||||
|  | 
 | ||||||
|  |         base.OnInitialized(); | ||||||
|  |     } | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     protected override void OnInitialized() |     /// <param name="firstRender"></param> | ||||||
|  |     protected override void OnAfterRender(bool firstRender) | ||||||
|     { |     { | ||||||
|         config?.Dispose(); |         if (firstRender) | ||||||
|         config = new TouchSocketConfig(); |         { | ||||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; |             var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace }; | ||||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); |             LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace }); | ||||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); |             config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||||
|         config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) }); |             config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) }); | ||||||
|         config.SetBufferLength(300); |             TcpServer.Setup(config); | ||||||
|         TcpServer = new TcpService(); |         } | ||||||
|         TcpServer.Setup(config); |         base.OnAfterRender(firstRender); | ||||||
|         base.OnInitialized(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); |     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 BlazorComponent; | ||||||
| @using Microsoft.AspNetCore.Components.Web; | @using Microsoft.AspNetCore.Components.Web; | ||||||
| @using System.IO.Ports; | @using System.IO.Ports; | ||||||
| @using System.Collections.Concurrent; | @using System.Collections.Concurrent; | ||||||
| @using ThingsGateway.Foundation; | @using ThingsGateway.Foundation.Core; | ||||||
| @using ThingsGateway.Foundation.Serial; |  | ||||||
| @using Masa.Blazor | @using Masa.Blazor | ||||||
| @using TouchSocket.Core; | 
 | ||||||
| @using TouchSocket.Sockets; | <MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%"> | ||||||
| @implements IDisposable |     <div class="mb-4">通道配置</div> | ||||||
| <MCard Class="pa-4" Flat Elevation="0" Rounded="false"> |     <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center"> | ||||||
|     <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start"> | 
 | ||||||
|         <MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip /> |         <MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP /> | ||||||
|         <MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port /> |         <MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port /> | ||||||
| 
 | 
 | ||||||
|         <MButton Class="ma-1" OnClick=Connect Color="primary"> |         <MButton Class="ma-1" OnClick=Connect Color="primary"> | ||||||
|             连接 |             连接 | ||||||
| @@ -32,8 +31,7 @@ | |||||||
|         <MButton Class="ma-1" OnClick=DisConnect Color="red"> |         <MButton Class="ma-1" OnClick=DisConnect Color="red"> | ||||||
|             断开 |             断开 | ||||||
|         </MButton> |         </MButton> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     </MRow> |     </MRow> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </MCard> | </MCard> | ||||||
| @@ -10,10 +10,7 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using TouchSocket.Core; | namespace ThingsGateway.Foundation.Demo; | ||||||
| using TouchSocket.Sockets; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Blazor; |  | ||||||
| 
 | 
 | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| public partial class UdpSessionPage : IDisposable | public partial class UdpSessionPage : IDisposable | ||||||
| @@ -24,18 +21,17 @@ public partial class UdpSessionPage : IDisposable | |||||||
|     public Action<LogLevel, object, string, Exception> LogAction; |     public Action<LogLevel, object, string, Exception> LogAction; | ||||||
| 
 | 
 | ||||||
|     private TouchSocketConfig config; |     private TouchSocketConfig config; | ||||||
| 
 |     /// <summary> | ||||||
|     private string ip = "127.0.0.1"; |     /// IP | ||||||
| 
 |     /// </summary> | ||||||
|     private int port = 502; |     public string IP = "127.0.0.1"; | ||||||
|  |     /// <summary> | ||||||
|  |     /// Port | ||||||
|  |     /// </summary> | ||||||
|  |     public int Port = 502; | ||||||
| 
 | 
 | ||||||
|     private UdpSession UdpSession { get; set; } = new(); |     private UdpSession UdpSession { get; set; } = new(); | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public void Dispose() |  | ||||||
|     { |  | ||||||
|         UdpSession.SafeDispose(); |  | ||||||
|     } |  | ||||||
|     private void Connect() |     private void Connect() | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
| @@ -68,32 +64,50 @@ public partial class UdpSessionPage : IDisposable | |||||||
|     public UdpSession GetUdpSession() |     public UdpSession GetUdpSession() | ||||||
|     { |     { | ||||||
|         config ??= new TouchSocketConfig(); |         config ??= new TouchSocketConfig(); | ||||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; |         var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace }; | ||||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); |         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace }); | ||||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); |         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); | ||||||
|         config.SetRemoteIPHost(new IPHost(ip + ":" + port)).SetBufferLength(300); |         config.SetRemoteIPHost(new IPHost(IP + ":" + Port)); | ||||||
|         config.SetBindIPHost(new IPHost(0)); |         config.SetBindIPHost(new IPHost(0)); | ||||||
|         //载入配置 |         //载入配置 | ||||||
|         UdpSession.Setup(config); |         UdpSession.Setup(config); | ||||||
|         return UdpSession; |         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> |     /// <summary> | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     protected override void OnInitialized() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         config?.Dispose(); |         UdpSession.SafeDispose(); | ||||||
|         config = new TouchSocketConfig(); |     } | ||||||
|         var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace }; |     internal void StateHasChangedAsync() | ||||||
|         LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace }); |     { | ||||||
|         config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage)); |         StateHasChanged(); | ||||||
|         config.SetRemoteIPHost(new IPHost(ip + ":" + port)).SetBufferLength(300); |  | ||||||
|         config.SetBindIPHost(new IPHost(0)); |  | ||||||
|         UdpSession = new UdpSession(); |  | ||||||
|         UdpSession.Setup(config); |  | ||||||
|         base.OnInitialized(); |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception); |  | ||||||
| } | } | ||||||
| @@ -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 | #endregion | ||||||
| 
 | 
 | ||||||
| global using System; | global using System; | ||||||
| global using System.Linq; |  | ||||||
| global using System.Threading; | global using System.Threading; | ||||||
| global using System.Threading.Tasks; | global using System.Threading.Tasks; | ||||||
| 
 | 
 | ||||||
| global using TouchSocket.Core; | global using ThingsGateway.Components; | ||||||
| global using TouchSocket.Sockets; | 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 | @namespace ThingsGateway.Foundation.Demo | ||||||
| @using Masa.Blazor.Presets | @using System.Text; | ||||||
| @using ThingsGateway.Admin.Blazor.Core |  | ||||||
| @inherits LayoutComponentBase | @inherits LayoutComponentBase | ||||||
| @layout BaseLayout | @layout BaseLayout | ||||||
| @if (UserManager.UserId > 0) | 
 | ||||||
| { |  | ||||||
|     <SysSignalR></SysSignalR> |  | ||||||
| } |  | ||||||
| <PPageTabsProvider> | <PPageTabsProvider> | ||||||
| 
 | 
 | ||||||
|     <CascadingValue Value="@this" IsFixed> |     <CascadingValue Value="@this" IsFixed> | ||||||
|         <CascadingValue Value="@Changed" Name="Changed"> |         <CascadingValue Value="@Changed" Name="Changed"> | ||||||
| 
 | 
 | ||||||
|             <MNavigationDrawer Color="barcolor" @bind-Value="_drawerOpen" App Width="200"> |             <MNavigationDrawer Color="barcolor" @bind-Value="_drawerOpen" App Width="200"> | ||||||
|                 @if (IsMobile) |                 <Logo CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT  CONFIG_TITLE=@CONFIG_TITLE HeightInt=@(IsMobile?BlazorResourceConst.AppBarHeight:BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight) /> | ||||||
|                 { |  | ||||||
|                     <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) /> |  | ||||||
|                 <AppList ClassString="overflow-y-auto" Routable |                 <AppList ClassString="overflow-y-auto" Routable | ||||||
|                          StyleString=@($"height: calc(100vh - {BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight}px);") |                          StyleString=@($"height: calc(100vh - {BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight}px);") | ||||||
|                          Items="Navs" /> |                          Items="Navs" /> | ||||||
| @@ -48,18 +32,16 @@ | |||||||
|                 <MButton Class="mr-0" Icon Small=IsMobile OnClick=@(() => _drawerOpen = !_drawerOpen)> |                 <MButton Class="mr-0" Icon Small=IsMobile OnClick=@(() => _drawerOpen = !_drawerOpen)> | ||||||
|                     <MIcon>mdi-menu</MIcon> |                     <MIcon>mdi-menu</MIcon> | ||||||
|                 </MButton> |                 </MButton> | ||||||
|                 <AppBarItems CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_COPYRIGHT_URL=@CONFIG_COPYRIGHT_URL CONFIG_TITLE=@CONFIG_TITLE> |  | ||||||
|                 </AppBarItems> |  | ||||||
| 
 | 
 | ||||||
|             </MAppBar> |             </MAppBar> | ||||||
| 
 | 
 | ||||||
|             <MMain Style=@($"{(!(IsMobile||_drawerOpen!=true)? "padding-left:200px;":"")}")> |             <MMain Style=@($"{(!(IsMobile||_drawerOpen!=true)? "padding-left:200px;":"")}")> | ||||||
|                 <div class="full-width"> |                 <div class="full-width"> | ||||||
|                     <PageTabs @ref="_pageTabs" /> |                     <PageTabs @ref="_pageTabs" PageTabItems="pageTabItems" /> | ||||||
|                 </div> |                 </div> | ||||||
|                 <MDivider Center></MDivider> |                 <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);")> |                 <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 |                         @Body | ||||||
|                     </PPageContainer> |                     </PPageContainer> | ||||||
|                 </MCard> |                 </MCard> | ||||||
| @@ -71,5 +53,25 @@ | |||||||
|     </CascadingValue> |     </CascadingValue> | ||||||
| 
 | 
 | ||||||
| </PPageTabsProvider> | </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> | <Project> | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<TargetFrameworks>net6.0;net7.0</TargetFrameworks> | 		<Version>3.0.0.13</Version> | ||||||
| 		<Version>2.0.9.3</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> | 		<Authors>Diego</Authors> | ||||||
| 		<Product>ThingsGateway</Product> | 		<Product>ThingsGateway</Product> | ||||||
| 		<Copyright>© 2023-present Diego</Copyright> | 		<Copyright>© 2023-present Diego</Copyright> | ||||||
| @@ -11,38 +16,38 @@ | |||||||
| 		<EmbedAllSources>true</EmbedAllSources> | 		<EmbedAllSources>true</EmbedAllSources> | ||||||
| 		<RepositoryType>Gitee</RepositoryType> | 		<RepositoryType>Gitee</RepositoryType> | ||||||
| 		<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | 		<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||||||
| 		<PackageReadmeFile>./README.md</PackageReadmeFile> | 		<PackageReadmeFile>README.md</PackageReadmeFile> | ||||||
|  | 		<PackageIcon>icon.png</PackageIcon> | ||||||
| 		<IncludeSymbols>true</IncludeSymbols> | 		<IncludeSymbols>true</IncludeSymbols> | ||||||
| 		<SymbolPackageFormat>snupkg</SymbolPackageFormat> |  | ||||||
| 		<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> | 		<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> | ||||||
| 		<PackageProjectUrl>https://diego2098.gitee.io/thingsgateway-docs/</PackageProjectUrl> | 		<PackageProjectUrl>https://diego2098.gitee.io/thingsgateway-docs/</PackageProjectUrl> | ||||||
| 		<PackageIcon>icon.png</PackageIcon> |  | ||||||
| 		<PackageTags>ThingsGateway;Diego;dotNET China;Blazor;设备采集;边缘网关</PackageTags> | 		<PackageTags>ThingsGateway;Diego;dotNET China;Blazor;设备采集;边缘网关</PackageTags> | ||||||
| 		<PackageOutputPath>../../../nupkgs</PackageOutputPath> |  | ||||||
| 		<SignAssembly>True</SignAssembly> | 		<SignAssembly>True</SignAssembly> | ||||||
| 		<DelaySign>False</DelaySign> | 		<DelaySign>False</DelaySign> | ||||||
| 		<AssemblyOriginatorKeyFile>..\..\..\..\snks\ThingsGateway.snk</AssemblyOriginatorKeyFile> |  | ||||||
| 		<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages> | 		<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages> | ||||||
|  | 		<PackageOutputPath>../../nupkgs</PackageOutputPath> | ||||||
|  | 		<AssemblyOriginatorKeyFile>../../../snks/ThingsGateway.snk</AssemblyOriginatorKeyFile> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 
 | 
 | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile> |  | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
| 
 | 
 | ||||||
| 	<PropertyGroup> |  | ||||||
| 		<LangVersion>latest</LangVersion> |  | ||||||
| 	</PropertyGroup> |  | ||||||
| 
 | 
 | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<None Include="..\..\..\..\README.md" Pack="true" PackagePath="\" /> | 		<None Include="..\..\..\README.md" Pack="true" PackagePath="\" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 	 | 
 | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<None Include="..\..\..\..\icon.png"> | 		<None Include="..\..\..\icon.png"> | ||||||
| 			<Pack>True</Pack> | 			<Pack>True</Pack> | ||||||
| 			<PackagePath></PackagePath> | 			<PackagePath></PackagePath> | ||||||
| 		</None> | 		</None> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
| 
 | 
 | ||||||
| 
 | 	<PropertyGroup Condition="'$(Configuration)' == 'Release'"> | ||||||
|  | 		<DebugSymbols>True</DebugSymbols> | ||||||
|  | 		<DebugType>Embedded</DebugType> | ||||||
|  | 		<EmbedAllSources>True</EmbedAllSources> | ||||||
|  | 	</PropertyGroup> | ||||||
|  | 	 | ||||||
| </Project> | </Project> | ||||||
| @@ -10,66 +10,47 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using BlazorComponent; | namespace ThingsGateway.Foundation.Adapter.DLT645; | ||||||
| namespace ThingsGateway.Admin.Blazor.Core; |  | ||||||
| 
 | 
 | ||||||
| /// <summary> | /// <summary> | ||||||
| /// ListItem | /// 控制码 | ||||||
| /// </summary> | /// </summary> | ||||||
| /// <typeparam name="TItem"></typeparam> | public enum ControlCode : byte | ||||||
| public interface IAppItem<TItem> |  | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 子菜单 |     /// 读数据 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     List<TItem> Children { get; } |     Read = 0x11, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否启用下划线 |     /// 读后续数据 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     bool Divider { get; set; } |     ReadSub = 0x12, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 菜单头部标题 |     /// 读站号 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     string Heading { get; } |     ReadStation = 0x13, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 链接 |     /// 写数据 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     string Href { get; set; } |     Write = 0x14, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 图标 |     /// 写站号 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     string Icon { get; set; } |     WriteStation = 0x15, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 菜单副标题 |     /// 广播校时 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     string SubTitle { get; set; } |     BroadcastTime = 0x08, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 跳转方式 |     /// 冻结 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     string Target { get; set; } |     Freeze = 0x16, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 菜单标题 |     /// 更新波特率 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     string Title { get; set; } |     WriteBaudRate = 0x17, | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 菜单值 |     /// 更新密码 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     StringNumber Value { get; set; } |     WritePassword = 0x18, | ||||||
| 
 |  | ||||||
|     /// <summary> |  | ||||||
|     /// 是否有子菜单 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     bool HasChildren() |  | ||||||
|     { |  | ||||||
|         return Children is not null && Children.Any(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
										
											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; | ||||||
| @@ -0,0 +1,175 @@ | |||||||
|  | #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; | ||||||
|  | using ThingsGateway.Foundation.Extension.String; | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Modbus协议地址 | ||||||
|  | /// </summary> | ||||||
|  | public class ModbusAddress : DeviceAddressBase | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     /// </summary> | ||||||
|  |     public ModbusAddress() | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 读取功能码 | ||||||
|  |     /// </summary> | ||||||
|  |     public ushort AddressStart => Address.ToUShort(); | ||||||
|  |     /// <summary> | ||||||
|  |     /// 读取功能码 | ||||||
|  |     /// </summary> | ||||||
|  |     public byte ReadFunction { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 站号信息 | ||||||
|  |     /// </summary> | ||||||
|  |     public byte Station { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 写入功能码 | ||||||
|  |     /// </summary> | ||||||
|  |     public byte WriteFunction { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 打包临时写入,需要读取的字节长度 | ||||||
|  |     /// </summary> | ||||||
|  |     public int ByteLength { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// BitIndex | ||||||
|  |     /// </summary> | ||||||
|  |     public int BitIndex => (int)(Address.SplitDot().LastOrDefault().ToInt()); | ||||||
|  |  | ||||||
|  |     /// <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) | ||||||
|  |     { | ||||||
|  |         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 | ||||||
|  |         { | ||||||
|  |             string[] strArray = address.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries); | ||||||
|  |             for (int index = 0; index < strArray.Length; ++index) | ||||||
|  |             { | ||||||
|  |                 if (strArray[index].ToUpper().StartsWith("S=")) | ||||||
|  |                 { | ||||||
|  |                     if (Convert.ToInt16(strArray[index].Substring(2)) > 0) | ||||||
|  |                         modbusAddress.Station = byte.Parse(strArray[index].Substring(2)); | ||||||
|  |                 } | ||||||
|  |                 else if (strArray[index].ToUpper().StartsWith("W=")) | ||||||
|  |                 { | ||||||
|  |                     if (Convert.ToInt16(strArray[index].Substring(2)) > 0) | ||||||
|  |                         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("=")) | ||||||
|  |                 { | ||||||
|  |                     Address(strArray[index]); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return modbusAddress; | ||||||
|  |  | ||||||
|  |         void Address(string address) | ||||||
|  |         { | ||||||
|  |             var readF = ushort.Parse(address.Substring(0, 1)); | ||||||
|  |             if (readF > 4) | ||||||
|  |                 throw new("功能码错误"); | ||||||
|  |             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() + ";"); | ||||||
|  |         } | ||||||
|  |         if (WriteFunction > 0) | ||||||
|  |         { | ||||||
|  |             stringGeter.Append("w=" + WriteFunction.ToString() + ";"); | ||||||
|  |         } | ||||||
|  |         if (!string.IsNullOrEmpty(SocketId)) | ||||||
|  |         { | ||||||
|  |             stringGeter.Append("id=" + SocketId + ";"); | ||||||
|  |         } | ||||||
|  |         stringGeter.Append(GetFunctionString(ReadFunction) + (AddressStart + 1).ToString()); | ||||||
|  |         return stringGeter.ToString(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private string GetFunctionString(int readF) | ||||||
|  |     { | ||||||
|  |         return readF switch | ||||||
|  |         { | ||||||
|  |             1 => "0", | ||||||
|  |             2 => "1", | ||||||
|  |             3 => "4", | ||||||
|  |             4 => "3", | ||||||
|  |             _ => "4", | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -13,7 +13,6 @@ | |||||||
| using System.Text; | using System.Text; | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Extension.Bool; | using ThingsGateway.Foundation.Extension.Bool; | ||||||
| using ThingsGateway.Foundation.Extension.Byte; |  | ||||||
| using ThingsGateway.Foundation.Extension.Generic; | using ThingsGateway.Foundation.Extension.Generic; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation.Adapter.Modbus; | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
| @@ -92,9 +91,17 @@ internal class ModbusHelper | |||||||
| 
 | 
 | ||||||
|             if (response[1] >= 0x80)//错误码 |             if (response[1] >= 0x80)//错误码 | ||||||
|                 return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success }; |                 return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success }; | ||||||
|             if ((response.Length < response[2] + 3)) |             if (response[1] <= 0x05) | ||||||
|                 return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache }; |             { | ||||||
|  |                 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) |             if (send.Length == 0) | ||||||
|             { |             { | ||||||
| @@ -125,9 +132,26 @@ internal class ModbusHelper | |||||||
|         if (response.Length < 3) |         if (response.Length < 3) | ||||||
|             return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache }; |             return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache }; | ||||||
| 
 | 
 | ||||||
|         if (crcCheck && !EasyCRC16.CheckCRC16(response)) |         if (response[1] >= 0x80)//错误码 | ||||||
|             return new OperResult<byte[], FilterResult>("Crc校验失败" + DataTransUtil.ByteToHexString(response, ' ')) { Content2 = FilterResult.Success }; |             return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success }; | ||||||
|         return GetModbusData(send, response.RemoveLast(2)); |         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> | ||||||
|     /// 获取读取报文 |     /// 获取读取报文 | ||||||
| @@ -136,8 +160,8 @@ internal class ModbusHelper | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             ModbusAddress mAddress = new(address, station); |             var mAddress = ModbusAddress.ParseFrom(address, station); | ||||||
|             return GetReadModbusCommand(mAddress, length); |             return OperResult.CreateSuccessResult(GetReadModbusCommand(mAddress, length)); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -151,7 +175,8 @@ internal class ModbusHelper | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             ModbusAddress mAddress = new(address, station); |             var mAddress = ModbusAddress.ParseFrom(address, station); | ||||||
|  | 
 | ||||||
|             //功能码或实际长度 |             //功能码或实际长度 | ||||||
|             if (values?.Length > 1 || mAddress.WriteFunction == 15) |             if (values?.Length > 1 || mAddress.WriteFunction == 15) | ||||||
|                 return GetWriteBoolModbusCommand(mAddress, values, values.Length); |                 return GetWriteBoolModbusCommand(mAddress, values, values.Length); | ||||||
| @@ -171,12 +196,13 @@ internal class ModbusHelper | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             ModbusAddress mAddress = new(address, station); |             var mAddress = ModbusAddress.ParseFrom(address, station); | ||||||
|  | 
 | ||||||
|             //功能码或实际长度 |             //功能码或实际长度 | ||||||
|             if (value?.Length > 2 || mAddress.WriteFunction == 16) |             if (value?.Length > 2 || mAddress.WriteFunction == 16) | ||||||
|                 return GetWriteModbusCommand(mAddress, value); |                 return OperResult.CreateSuccessResult(GetWriteModbusCommand(mAddress, value)); | ||||||
|             else |             else | ||||||
|                 return GetWriteOneModbusCommand(mAddress, value); |                 return OperResult.CreateSuccessResult(GetWriteOneModbusCommand(mAddress, value)); | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
| @@ -187,7 +213,7 @@ internal class ModbusHelper | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取读取报文 |     /// 获取读取报文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static OperResult<byte[]> GetReadModbusCommand(ModbusAddress mAddress, int length) |     internal static byte[] GetReadModbusCommand(ModbusAddress mAddress, int length) | ||||||
|     { |     { | ||||||
|         byte[] array = new byte[6] |         byte[] array = new byte[6] | ||||||
|         { |         { | ||||||
| @@ -198,21 +224,21 @@ internal class ModbusHelper | |||||||
|         BitConverter.GetBytes(length)[1], |         BitConverter.GetBytes(length)[1], | ||||||
|         BitConverter.GetBytes(length)[0] |         BitConverter.GetBytes(length)[0] | ||||||
|         }; |         }; | ||||||
| 
 |         return array; | ||||||
|         return OperResult.CreateSuccessResult(array); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取05写入布尔量报文 |     /// 获取05写入布尔量报文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static OperResult<byte[]> GetWriteBoolModbusCommand(string address, bool value, byte station) |     internal static OperResult<byte[]> GetWriteBoolModbusCommand(string address, bool value, byte station) | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             if (address.IndexOf('.') <= 0) |             if (address.IndexOf('.') <= 0) | ||||||
|             { |             { | ||||||
|                 ModbusAddress mAddress = new(address, station); |                 var mAddress = ModbusAddress.ParseFrom(address, station); | ||||||
|                 return GetWriteBoolModbusCommand(mAddress, value); | 
 | ||||||
|  |                 return OperResult.CreateSuccessResult(GetWriteBoolModbusCommand(mAddress, value)); | ||||||
|             } |             } | ||||||
|             return new("不支持写入字寄存器的某一位"); |             return new("不支持写入字寄存器的某一位"); | ||||||
|         } |         } | ||||||
| @@ -225,7 +251,7 @@ internal class ModbusHelper | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取05写入布尔量报文 |     /// 获取05写入布尔量报文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static OperResult<byte[]> GetWriteBoolModbusCommand(ModbusAddress mAddress, bool value) |     private static byte[] GetWriteBoolModbusCommand(ModbusAddress mAddress, bool value) | ||||||
|     { |     { | ||||||
|         byte[] array = new byte[6] |         byte[] array = new byte[6] | ||||||
|         { |         { | ||||||
| @@ -246,13 +272,13 @@ internal class ModbusHelper | |||||||
|             array[4] = 0; |             array[4] = 0; | ||||||
|             array[5] = 0; |             array[5] = 0; | ||||||
|         } |         } | ||||||
|         return OperResult.CreateSuccessResult(array); |         return array; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取15写入布尔量报文 |     /// 获取15写入布尔量报文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static OperResult<byte[]> GetWriteBoolModbusCommand(ModbusAddress mAddress, bool[] values, int length) |     internal static OperResult<byte[]> GetWriteBoolModbusCommand(ModbusAddress mAddress, bool[] values, int length) | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
| @@ -277,7 +303,7 @@ internal class ModbusHelper | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取16写入字报文 |     /// 获取16写入字报文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static OperResult<byte[]> GetWriteModbusCommand(ModbusAddress mAddress, byte[] values) |     internal static byte[] GetWriteModbusCommand(ModbusAddress mAddress, byte[] values) | ||||||
|     { |     { | ||||||
|         byte[] numArray = new byte[7 + values.Length]; |         byte[] numArray = new byte[7 + values.Length]; | ||||||
|         numArray[0] = (byte)mAddress.Station; |         numArray[0] = (byte)mAddress.Station; | ||||||
| @@ -288,14 +314,13 @@ internal class ModbusHelper | |||||||
|         numArray[5] = (byte)(values.Length / 2 % 256); |         numArray[5] = (byte)(values.Length / 2 % 256); | ||||||
|         numArray[6] = (byte)values.Length; |         numArray[6] = (byte)values.Length; | ||||||
|         values.CopyTo(numArray, 7); |         values.CopyTo(numArray, 7); | ||||||
|         return OperResult.CreateSuccessResult(numArray); |         return numArray; | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取6写入字报文 |     /// 获取6写入字报文 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private static OperResult<byte[]> GetWriteOneModbusCommand(ModbusAddress mAddress, byte[] values) |     internal static byte[] GetWriteOneModbusCommand(ModbusAddress mAddress, byte[] values) | ||||||
|     { |     { | ||||||
|         byte[] numArray = new byte[4 + values.Length]; |         byte[] numArray = new byte[4 + values.Length]; | ||||||
|         numArray[0] = (byte)mAddress.Station; |         numArray[0] = (byte)mAddress.Station; | ||||||
| @@ -303,7 +328,7 @@ internal class ModbusHelper | |||||||
|         numArray[2] = BitConverter.GetBytes(mAddress.AddressStart)[1]; |         numArray[2] = BitConverter.GetBytes(mAddress.AddressStart)[1]; | ||||||
|         numArray[3] = BitConverter.GetBytes(mAddress.AddressStart)[0]; |         numArray[3] = BitConverter.GetBytes(mAddress.AddressStart)[0]; | ||||||
|         values.CopyTo(numArray, 4); |         values.CopyTo(numArray, 4); | ||||||
|         return OperResult.CreateSuccessResult(numArray); |         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,6 +10,7 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
|  | using ThingsGateway.Foundation.Extension.Generic; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation.Adapter.Modbus; | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
| 
 | 
 | ||||||
| @@ -42,20 +43,43 @@ public class ModbusRtuDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<M | |||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override 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 (send?.Length > 0) | ||||||
|         if (result.IsSuccess) |  | ||||||
|         { |         { | ||||||
|             request.ResultCode = result.ResultCode; |             int index = -1; | ||||||
|             request.Message = result.Message; |             for (int i = 0; i < response.Length - 1; i++) | ||||||
|             request.Content = result.Content1; |             { | ||||||
|  |                 if (response[i] == send[0] && (response[i + 1] == send[1] || response[i + 1] == (send[1] + 0x80))) | ||||||
|  |                 { | ||||||
|  |                     index = i; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (index >= 0) | ||||||
|  |             { | ||||||
|  |                 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 | ||||||
|  |             { | ||||||
|  |                 request.ErrorCode = result.ErrorCode; | ||||||
|  |                 request.Message = result.Message; | ||||||
|  |             } | ||||||
|  |             return result.Content2; | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             request.ResultCode = result.ResultCode; |             return FilterResult.Success; | ||||||
|             request.Message = result.Message; |  | ||||||
|         } |         } | ||||||
|         return result.Content2; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| } | } | ||||||
| @@ -20,15 +20,12 @@ public class ModbusRtuMessage : MessageBase, IMessage | |||||||
|     public override int HeadBytesLength => -1; |     public override int HeadBytesLength => -1; | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override bool CheckHeadBytes(byte[] head) |     public override bool CheckHeadBytes(byte[] heads) | ||||||
|     { |     { | ||||||
|  |         BodyLength = -1; | ||||||
|         return true; |         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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -10,8 +10,6 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Extension; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Foundation.Adapter.Modbus; | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
| 
 | 
 | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -37,10 +35,9 @@ public class ModbusRtuOverUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAd | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override OperResult<byte[]> UnpackResponse( |     protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response) | ||||||
|               byte[] send, byte[] response) |  | ||||||
|     { |     { | ||||||
|         var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable); |         var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable); | ||||||
|         return result.Copy(); |         return result; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,421 @@ | |||||||
|  | #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) | ||||||
|  |     { | ||||||
|  |         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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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.Message); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -55,13 +55,13 @@ public class ModbusTcpDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<M | |||||||
|         var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6)); |         var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6)); | ||||||
|         if (result.IsSuccess) |         if (result.IsSuccess) | ||||||
|         { |         { | ||||||
|             request.ResultCode = result.ResultCode; |             request.ErrorCode = result.ErrorCode; | ||||||
|             request.Message = result.Message; |             request.Message = result.Message; | ||||||
|             request.Content = result.Content1; |             request.Content = result.Content; | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             request.ResultCode = result.ResultCode; |             request.ErrorCode = result.ErrorCode; | ||||||
|             request.Message = result.Message; |             request.Message = result.Message; | ||||||
|         } |         } | ||||||
|         return result.Content2; |         return result.Content2; | ||||||
| @@ -23,10 +23,10 @@ public class ModbusTcpMessage : MessageBase, IMessage | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public bool IsCheckMessageId { get; set; } = false; |     public bool IsCheckMessageId { get; set; } = false; | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override bool CheckHeadBytes(byte[] head) |     public override bool CheckHeadBytes(byte[] heads) | ||||||
|     { |     { | ||||||
|         if (head == null || head.Length <= 0) return false; |         if (heads == null || heads.Length <= 0) return false; | ||||||
|         HeadBytes = head; |         HeadBytes = heads; | ||||||
| 
 | 
 | ||||||
|         int num = (HeadBytes[4] * 256) + HeadBytes[5]; |         int num = (HeadBytes[4] * 256) + HeadBytes[5]; | ||||||
|         BodyLength = num; |         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.GetWaitingClient(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.GetWaitingClient(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 | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
|  | using System.ComponentModel; | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Extension.Bool; | using ThingsGateway.Foundation.Extension.Bool; | ||||||
| using ThingsGateway.Foundation.Extension.Byte; |  | ||||||
| using ThingsGateway.Foundation.Extension.Generic; | using ThingsGateway.Foundation.Extension.Generic; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation.Adapter.Modbus; | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
| /// <summary> | /// <summary> | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| /// </summary> | /// </summary> | ||||||
| public class ModbusServer : ReadWriteDevicesTcpServerBase | public class ModbusTcpServer : ReadWriteDevicesTcpServerBase | ||||||
| { | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端 | ||||||
|  |     /// </summary> | ||||||
|  |     public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SocketClient, Task<OperResult>> WriteData; | ||||||
|  | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 继电器 |     /// 继电器 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new(); |     private ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new(); | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 开关输入 |     /// 开关输入 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new(); |     private ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new(); | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 输入寄存器 |     /// 输入寄存器 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new(); |     private ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new(); | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 保持寄存器 |     /// 保持寄存器 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new(); |     private ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new(); | ||||||
|     /// <summary> |  | ||||||
|     /// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端 |  | ||||||
|     /// </summary> |  | ||||||
|     public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SocketClient, OperResult> WriteData; |  | ||||||
| 
 |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public ModbusServer(TcpService tcpService) : base(tcpService) |     public ModbusTcpServer(TcpService tcpService) : base(tcpService) | ||||||
|     { |     { | ||||||
|         ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big); |         ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big); | ||||||
|         RegisterByteLength = 2; |         RegisterByteLength = 2; | ||||||
| @@ -56,46 +56,18 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 多站点 |     /// 多站点 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|  |     [Description("多站点")] | ||||||
|     public bool MulStation { get; set; } |     public bool MulStation { get; set; } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 默认站点 |     /// 默认站点 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|  |     [Description("默认站点")] | ||||||
|     public byte Station { get; set; } = 1; |     public byte Station { get; set; } = 1; | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |  | ||||||
|     public override string GetAddressDescription() |  | ||||||
|     { |  | ||||||
|         return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription(); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token = default) |     public override void Dispose() | ||||||
|     { |  | ||||||
|         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) |  | ||||||
|     { |     { | ||||||
|         foreach (var item in ModbusServer01ByteBlocks) |         foreach (var item in ModbusServer01ByteBlocks) | ||||||
|         { |         { | ||||||
| @@ -117,167 +89,28 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase | |||||||
|         ModbusServer02ByteBlocks.Clear(); |         ModbusServer02ByteBlocks.Clear(); | ||||||
|         ModbusServer03ByteBlocks.Clear(); |         ModbusServer03ByteBlocks.Clear(); | ||||||
|         ModbusServer04ByteBlocks.Clear(); |         ModbusServer04ByteBlocks.Clear(); | ||||||
|         Disconnect(); |         base.Dispose(); | ||||||
|         base.Dispose(disposing); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override void Received(SocketClient client, IRequestInfo requestInfo) |     public override string GetAddressDescription() | ||||||
|     { |     { | ||||||
|         //接收外部报文 |         return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.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); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     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); |         return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack); | ||||||
|         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); |  | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 |     /// <inheritdoc/> | ||||||
|     private OperResult<byte[]> Read(string address, int length) |     public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         ModbusAddress mAddress; |         ModbusAddress mAddress; | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             mAddress = new ModbusAddress(address, Station); |             mAddress = ModbusAddress.ParseFrom(address, Station); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -328,12 +161,42 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase | |||||||
|         } |         } | ||||||
|         return new OperResult<byte[]>("功能码错误"); |         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; |         ModbusAddress mAddress; | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             mAddress = new ModbusAddress(address, Station); |             mAddress = ModbusAddress.ParseFrom(address, Station); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -366,12 +229,14 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase | |||||||
|         } |         } | ||||||
|         return new OperResult("功能码错误"); |         return new OperResult("功能码错误"); | ||||||
|     } |     } | ||||||
|     private OperResult Write(string address, bool[] value) | 
 | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         ModbusAddress mAddress; |         ModbusAddress mAddress; | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             mAddress = new ModbusAddress(address, Station); |             mAddress = ModbusAddress.ParseFrom(address, Station); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -407,4 +272,170 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase | |||||||
|         } |         } | ||||||
|         return new OperResult("功能码错误"); |         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) | ||||||
|  |     { | ||||||
|  |         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); | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -15,7 +15,7 @@ using ThingsGateway.Foundation.Extension.Generic; | |||||||
| namespace ThingsGateway.Foundation.Adapter.Modbus; | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
| 
 | 
 | ||||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||||
| public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusServerMessage> | public class ModbusTcpServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusTcpServerMessage> | ||||||
| { | { | ||||||
|     private readonly ThingsGatewayBitConverter ThingsGatewayBitConverter = new(EndianType.Big); |     private readonly ThingsGatewayBitConverter ThingsGatewayBitConverter = new(EndianType.Big); | ||||||
| 
 | 
 | ||||||
| @@ -53,7 +53,7 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return new OperResult<byte[]>(response) { Message = $"数据长度{response.Length}错误" }; |             return new OperResult<byte[]>() { Message = $"数据长度{response.Length}错误" }; | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
| @@ -62,13 +62,13 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override ModbusServerMessage GetInstance() |     protected override ModbusTcpServerMessage GetInstance() | ||||||
|     { |     { | ||||||
|         return new ModbusServerMessage(); |         return new ModbusTcpServerMessage(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override 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)); |         var result = GetModbusData(response.RemoveBegin(6)); | ||||||
|         if (result.IsSuccess) |         if (result.IsSuccess) | ||||||
| @@ -88,11 +88,11 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte | |||||||
|                     request.CurModbusAddress = new ModbusAddress() |                     request.CurModbusAddress = new ModbusAddress() | ||||||
|                     { |                     { | ||||||
|                         Station = station, |                         Station = station, | ||||||
|                         AddressStart = addressStart, |                         Address = addressStart.ToString(), | ||||||
|                         WriteFunction = function, |                         WriteFunction = function, | ||||||
|                         ReadFunction = (byte)(function == 16 ? 3 : function == 15 ? 1 : 3), |                         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); |                     request.Content = result.Content.RemoveBegin(7); | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
| @@ -100,11 +100,11 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte | |||||||
|                     request.CurModbusAddress = new ModbusAddress() |                     request.CurModbusAddress = new ModbusAddress() | ||||||
|                     { |                     { | ||||||
|                         Station = station, |                         Station = station, | ||||||
|                         AddressStart = addressStart, |                         Address = addressStart.ToString(), | ||||||
|                         WriteFunction = function, |                         WriteFunction = function, | ||||||
|                         ReadFunction = (byte)(function == 6 ? 3 : function == 5 ? 1 : 3), |                         ReadFunction = (byte)(function == 6 ? 3 : function == 5 ? 1 : 3), | ||||||
|                         Length = 1, |  | ||||||
|                     }; |                     }; | ||||||
|  |                     request.Length = 1; | ||||||
|                     request.Content = result.Content.RemoveBegin(4); |                     request.Content = result.Content.RemoveBegin(4); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -113,18 +113,18 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte | |||||||
|                 request.CurModbusAddress = new ModbusAddress() |                 request.CurModbusAddress = new ModbusAddress() | ||||||
|                 { |                 { | ||||||
|                     Station = station, |                     Station = station, | ||||||
|                     AddressStart = addressStart, |                     Address = addressStart.ToString(), | ||||||
|                     ReadFunction = function, |                     ReadFunction = function, | ||||||
|                     Length = ThingsGatewayBitConverter.ToByte(response, 11), |  | ||||||
|                 }; |                 }; | ||||||
|  |                 request.Length = ThingsGatewayBitConverter.ToByte(response, 11); | ||||||
|             } |             } | ||||||
|             request.ResultCode = result.ResultCode; |             request.ErrorCode = result.ErrorCode; | ||||||
|             request.Message = result.Message; |             request.Message = result.Message; | ||||||
|             return FilterResult.Success; |             return FilterResult.Success; | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             request.ResultCode = result.ResultCode; |             request.ErrorCode = result.ErrorCode; | ||||||
|             request.Message = result.Message; |             request.Message = result.Message; | ||||||
|             return FilterResult.Cache; |             return FilterResult.Cache; | ||||||
|         } |         } | ||||||
| @@ -15,20 +15,24 @@ namespace ThingsGateway.Foundation.Adapter.Modbus | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public class ModbusServerMessage : MessageBase, IMessage |     public class ModbusTcpServerMessage : MessageBase, IMessage | ||||||
|     { |     { | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// 当前关联的地址 |         /// 当前关联的地址 | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public ModbusAddress CurModbusAddress { get; set; } |         public ModbusAddress CurModbusAddress { get; set; } | ||||||
|  |         /// <summary> | ||||||
|  |         /// 当前读写的数据长度 | ||||||
|  |         /// </summary> | ||||||
|  |         public int Length { get; set; } | ||||||
| 
 | 
 | ||||||
|         /// <inheritdoc/> |         /// <inheritdoc/> | ||||||
|         public override int HeadBytesLength => 6; |         public override int HeadBytesLength => 6; | ||||||
|         /// <inheritdoc/> |         /// <inheritdoc/> | ||||||
|         public override bool CheckHeadBytes(byte[] head) |         public override bool CheckHeadBytes(byte[] heads) | ||||||
|         { |         { | ||||||
|             if (head == null || head.Length != 6) return false; |             if (heads == null || heads.Length != 6) return false; | ||||||
|             HeadBytes = head; |             HeadBytes = heads; | ||||||
| 
 | 
 | ||||||
|             int num = (HeadBytes[4] * 256) + HeadBytes[5]; |             int num = (HeadBytes[4] * 256) + HeadBytes[5]; | ||||||
|             BodyLength = num; |             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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -10,7 +10,6 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Extension; |  | ||||||
| using ThingsGateway.Foundation.Extension.Generic; | using ThingsGateway.Foundation.Extension.Generic; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation.Adapter.Modbus; | namespace ThingsGateway.Foundation.Adapter.Modbus; | ||||||
| @@ -52,6 +51,6 @@ public class ModbusUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<M | |||||||
|     protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response) |     protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response) | ||||||
|     { |     { | ||||||
|         var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6)); |         var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6)); | ||||||
|         return result.Copy(); |         return result; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -10,131 +10,138 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #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.Generic; | ||||||
| using ThingsGateway.Foundation.Extension.String; | 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 byteConverter = device.ThingsGatewayBitConverter; | ||||||
|         var result = new List<DeviceVariableSourceRead>(); |  | ||||||
|         //需要先剔除额外信息,比如dataformat等 |         //需要先剔除额外信息,比如dataformat等 | ||||||
|         foreach (var item in deviceVariables) |         foreach (var item in deviceVariables) | ||||||
|         { |         { | ||||||
|             var address = item.VariableAddress; |             var address = item.VariableAddress; | ||||||
| 
 |  | ||||||
|             IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, byteConverter); |             IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, byteConverter); | ||||||
|             item.ThingsGatewayBitConverter = transformParameter; |             item.ThingsGatewayBitConverter = transformParameter; | ||||||
|             item.VariableAddress = address; |             //item.VariableAddress = address; | ||||||
|             item.Index = device.GetBitOffset(item.VariableAddress); |             item.Index = device.GetBitOffset(item.VariableAddress); | ||||||
|         } |         } | ||||||
| 
 |         var deviceVariableRunTimeGroups = deviceVariables.GroupBy(it => it.IntervalTime); | ||||||
|         //按读取间隔分组 |         foreach (var group in deviceVariableRunTimeGroups) | ||||||
|         var tags = deviceVariables.GroupBy(it => it.IntervalTime); |  | ||||||
|         foreach (var item in tags) |  | ||||||
|         { |         { | ||||||
|             Dictionary<ModbusAddress, DeviceVariableRunTime> map = item.ToDictionary(it => |             Dictionary<ModbusAddress, T2> map = group.ToDictionary(it => | ||||||
|             { |             { | ||||||
|                 var lastLen = it.DataTypeEnum.GetByteLength(); |                 var lastLen = it.DataTypeEnum.GetByteLength(); | ||||||
|                 if (lastLen <= 0) |                 if (lastLen <= 0) | ||||||
|                 { |                 { | ||||||
|                     if (it.DataTypeEnum.GetSystemType() == typeof(bool)) |                     switch (it.DataTypeEnum) | ||||||
|                     { |                     { | ||||||
|                         lastLen = 2; |                         case DataTypeEnum.String: | ||||||
|                     } |                             lastLen = it.ThingsGatewayBitConverter.Length == null ? throw new("数据类型为字符串时,必须指定字符串长度,才能进行打包") : it.ThingsGatewayBitConverter.Length.Value; | ||||||
|                     else if (it.DataTypeEnum.GetSystemType() == typeof(string)) |                             break; | ||||||
|                     { |                         default: | ||||||
|                         lastLen = it.ThingsGatewayBitConverter.StringLength; |                             lastLen = 2; | ||||||
|                     } |                             break; | ||||||
|                     else if (it.DataTypeEnum.GetSystemType() == typeof(object)) |  | ||||||
|                     { |  | ||||||
|                         lastLen = 2; |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 if (it.ThingsGatewayBitConverter.Length != null && it.DataTypeEnum != DataTypeEnum.String) | ||||||
|  |                 { | ||||||
|  |                     lastLen *= it.ThingsGatewayBitConverter.Length.Value; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 var address = it.VariableAddress; |                 var address = it.VariableAddress; | ||||||
|                 if (address.IndexOf('.') > 0) |                 if (address.IndexOf('.') > 0) | ||||||
|                 { |                 { | ||||||
|                     var addressSplits = address.SplitDot(); |                     var addressSplits = address.SplitDot(); | ||||||
| 
 |  | ||||||
|                     address = addressSplits.RemoveLast(1).ArrayToString("."); |                     address = addressSplits.RemoveLast(1).ArrayToString("."); | ||||||
|                 } |                 } | ||||||
| 
 |                 var result = ModbusAddress.ParseFrom(address); | ||||||
|                 var result = new ModbusAddress(address, (ushort)lastLen); |                 result.ByteLength = lastLen; | ||||||
|                 if (result == null) |  | ||||||
|                 { |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return result; |                 return result; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             //获取变量的地址 |             //获取变量的地址 | ||||||
|             var modbusAddressList = map.Keys.ToList(); |             var modbusAddressList = map.Keys; | ||||||
| 
 | 
 | ||||||
|             //获取功能码 |             //获取功能码 | ||||||
|             var functionCodes = modbusAddressList.Select(t => t.ReadFunction).Distinct(); |             var functionCodes = modbusAddressList.Select(t => t.ReadFunction).Distinct(); | ||||||
|             foreach (var functionCode in functionCodes) |             foreach (var functionCode in functionCodes) | ||||||
|             { |             { | ||||||
|                 var modbusAddressSameFunList = modbusAddressList |                 var modbusAddressSameFunList = modbusAddressList.Where(t => t.ReadFunction == functionCode); | ||||||
|                     .Where(t => t.ReadFunction == functionCode).ToList(); |                 var stationNumbers = modbusAddressSameFunList.Select(t => t.Station).Distinct(); | ||||||
|                 var stationNumbers = modbusAddressSameFunList |  | ||||||
|                     .Select(t => t.Station).Distinct().ToList(); |  | ||||||
|                 foreach (var stationNumber in stationNumbers) |                 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]); |                         .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) |         while (maxAddress >= minAddress) | ||||||
|         { |         { | ||||||
|             //最大的打包长度 |             //最大的打包长度 | ||||||
|             int readLength = MaxPack; |             int readLength = maxPack; | ||||||
|             if (functionCode == 1 || functionCode == 2) |             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 |                 Length = sourceLen | ||||||
|             }; |             }; | ||||||
|             foreach (var item in tempAddressAndLen) |             foreach (var item in tempAddressEnd) | ||||||
|             { |             { | ||||||
|                 var readNode = addressList[item]; |                 var readNode = addressList[item]; | ||||||
|                 if ((functionCode == -1 || functionCode == 3 || functionCode == 4) && |                 if ((functionCode == -1 || functionCode == 3 || functionCode == 4) && readNode.DataTypeEnum == DataTypeEnum.Boolean) | ||||||
|                     readNode.DataTypeEnum == DataTypeEnum.Boolean) |  | ||||||
|                 { |                 { | ||||||
|                     readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 16) + readNode.Index; |                     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; |                         readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 2) + readNode.Index; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| 
 |                 sourceRead.DeviceVariableRunTimes.Add(readNode); | ||||||
|                 sourceRead.DeviceVariables.Add(readNode); |                 addressList.Remove(item); | ||||||
|                 orderByAddressAndLen.Remove(item); |  | ||||||
|                 orderByAddress.Remove(item); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             sourceReads.Add(sourceRead); |             sourceReads.Add(sourceRead); | ||||||
|             if (orderByAddressAndLen.Count > 0) |             if (orderByAddressEnd.Count() > 0) | ||||||
|                 minAddress = orderByAddress.First().AddressStart; |                 minAddress = orderByAddressStart.First().AddressStart; | ||||||
|             else |             else | ||||||
|                 break; |                 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/> |             <inheritdoc/> | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.#ctor(System.String,System.UInt16)"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.AddressStart"> | ||||||
|             <inheritdoc/> |             <summary> | ||||||
|         </member> |             读取功能码 | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.#ctor(System.String,System.Byte)"> |             </summary> | ||||||
|             <inheritdoc/> |  | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ReadFunction"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ReadFunction"> | ||||||
|             <summary> |             <summary> | ||||||
| @@ -35,16 +34,28 @@ | |||||||
|             写入功能码 |             写入功能码 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ParseFrom(System.String,System.Int32)"> |         <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> | ||||||
|             解析地址 |             解析地址 | ||||||
|             </summary> |             </summary> | ||||||
|             <param name="address"></param> |  | ||||||
|             <param name="length"></param> |  | ||||||
|             <returns></returns> |  | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ToString"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ToString"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
| @@ -135,24 +146,14 @@ | |||||||
|         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.CacheTimeout"> |  | ||||||
|             <summary> |  | ||||||
|             组包缓存时间/ms |  | ||||||
|             </summary> |  | ||||||
|         </member> |  | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Crc16CheckEnable"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Crc16CheckEnable"> | ||||||
|             <summary> |             <summary> | ||||||
|             Crc校验 |             Crc校验 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.FrameTime"> |  | ||||||
|             <summary> |  | ||||||
|             帧前时间ms |  | ||||||
|             </summary> |  | ||||||
|         </member> |  | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Station"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Station"> | ||||||
|             <summary> |             <summary> | ||||||
|             站号 |             站号 | ||||||
| @@ -161,10 +162,22 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.GetAddressDescription"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.GetAddressDescription"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> | ||||||
| @@ -173,13 +186,10 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Crc16CheckEnable"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Crc16CheckEnable"> | ||||||
| @@ -187,11 +197,6 @@ | |||||||
|             Crc校验 |             Crc校验 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.FrameTime"> |  | ||||||
|             <summary> |  | ||||||
|             帧前时间ms |  | ||||||
|             </summary> |  | ||||||
|         </member> |  | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Station"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Station"> | ||||||
|             <summary> |             <summary> | ||||||
|             站号 |             站号 | ||||||
| @@ -200,10 +205,22 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.GetAddressDescription"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.GetAddressDescription"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> | ||||||
| @@ -212,9 +229,6 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdpDataHandleAdapter"> | ||||||
|             <summary> |             <summary> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
| @@ -239,27 +253,17 @@ | |||||||
|             ModbusRtu |             ModbusRtu | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </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> |             <summary> | ||||||
|             ModbusRtu |             ModbusRtu | ||||||
|             </summary> |             </summary> | ||||||
|             <param name="serialClient"></param> |             <param name="serialSession"></param> | ||||||
|         </member> |  | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.CacheTimeout"> |  | ||||||
|             <summary> |  | ||||||
|             组包缓存时间/ms |  | ||||||
|             </summary> |  | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Crc16CheckEnable"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Crc16CheckEnable"> | ||||||
|             <summary> |             <summary> | ||||||
|             Crc校验 |             Crc校验 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.FrameTime"> |  | ||||||
|             <summary> |  | ||||||
|             帧前时间ms |  | ||||||
|             </summary> |  | ||||||
|         </member> |  | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Station"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Station"> | ||||||
|             <summary> |             <summary> | ||||||
|             站号 |             站号 | ||||||
| @@ -268,10 +272,22 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.GetAddressDescription"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.GetAddressDescription"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> | ||||||
| @@ -347,7 +363,7 @@ | |||||||
|             接收外部写入时,传出变量地址/写入字节组/转换规则/客户端 |             接收外部写入时,传出变量地址/写入字节组/转换规则/客户端 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.MulStation"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.MulStation"> | ||||||
| @@ -360,13 +376,28 @@ | |||||||
|             默认站点 |             默认站点 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </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"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.GetAddressDescription"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> | ||||||
| @@ -375,10 +406,7 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Dispose(System.Boolean)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Received(ThingsGateway.Foundation.Sockets.SocketClient,ThingsGateway.Foundation.Core.IRequestInfo)"> | ||||||
|             <inheritdoc/> |  | ||||||
|         </member> |  | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Received(TouchSocket.Sockets.SocketClient,TouchSocket.Sockets.IRequestInfo)"> |  | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerDataHandleAdapter"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerDataHandleAdapter"> | ||||||
| @@ -414,6 +442,11 @@ | |||||||
|             当前关联的地址 |             当前关联的地址 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </member> | ||||||
|  |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.Length"> | ||||||
|  |             <summary> | ||||||
|  |             当前读写的数据长度 | ||||||
|  |             </summary> | ||||||
|  |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.HeadBytesLength"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.HeadBytesLength"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
| @@ -423,19 +456,9 @@ | |||||||
|         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </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"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.IsCheckMessageId"> | ||||||
|             <summary> |             <summary> | ||||||
|             检测事务标识符 |             检测事务标识符 | ||||||
| @@ -449,10 +472,22 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.GetAddressDescription"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.GetAddressDescription"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> | ||||||
| @@ -461,9 +496,6 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcpDataHandleAdapter"> | ||||||
|             <summary> |             <summary> | ||||||
|             ModbusTcpDataHandleAdapter |             ModbusTcpDataHandleAdapter | ||||||
| @@ -502,14 +534,9 @@ | |||||||
|         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.FrameTime"> |  | ||||||
|             <summary> |  | ||||||
|             帧前时间ms |  | ||||||
|             </summary> |  | ||||||
|         </member> |  | ||||||
|         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.IsCheckMessageId"> |         <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.IsCheckMessageId"> | ||||||
|             <summary> |             <summary> | ||||||
|             检测事务标识符 |             检测事务标识符 | ||||||
| @@ -520,13 +547,25 @@ | |||||||
|             站号 |             站号 | ||||||
|             </summary> |             </summary> | ||||||
|         </member> |         </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"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.GetAddressDescription"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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/> |             <inheritdoc/> | ||||||
|         </member> |         </member> | ||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)"> | ||||||
| @@ -535,9 +574,6 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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"> |         <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter"> | ||||||
|             <summary> |             <summary> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
| @@ -557,5 +593,19 @@ | |||||||
|         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter.UnpackResponse(System.Byte[],System.Byte[])"> |         <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter.UnpackResponse(System.Byte[],System.Byte[])"> | ||||||
|             <inheritdoc/> |             <inheritdoc/> | ||||||
|         </member> |         </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> |     </members> | ||||||
| </doc> | </doc> | ||||||
| @@ -10,7 +10,6 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | ||||||
| @@ -10,8 +10,6 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Foundation.Adapter.OPCDA.Da; | namespace ThingsGateway.Foundation.Adapter.OPCDA.Da; | ||||||
| 
 | 
 | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -10,10 +10,7 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| using System.Text; |  | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | ||||||
| 
 | 
 | ||||||
| @@ -201,7 +198,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|         OnWriteCompleted?.Invoke(itemwrite); |         OnWriteCompleted?.Invoke(itemwrite); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     internal OperResult AddOpcItem(OpcItem[] items) |     internal List<Tuple<OpcItem, int>> AddOpcItem(OpcItem[] items) | ||||||
|     { |     { | ||||||
|         IntPtr pResults = IntPtr.Zero; |         IntPtr pResults = IntPtr.Zero; | ||||||
|         IntPtr pErrors = 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); |             m_ItemManagement?.AddItems(items.Length, itemDefyArray, out pResults, out pErrors); | ||||||
|             IntPtr Pos = pResults; |             IntPtr Pos = pResults; | ||||||
|             Marshal.Copy(pErrors, errors, 0, items.Length); |             Marshal.Copy(pErrors, errors, 0, items.Length); | ||||||
|             StringBuilder stringBuilder = new(); |             List<Tuple<OpcItem, int>> results = new(); | ||||||
|             for (int j = 0; j < items.Length; j++) |             for (int j = 0; j < items.Length; j++) | ||||||
|             { |             { | ||||||
|                 if (errors[j] == 0) |                 if (errors[j] == 0) | ||||||
| @@ -250,21 +247,10 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     stringBuilder.AppendLine(items[j].ItemID + "添加失败,错误代码:" + errors[j]); |                     results.Add(Tuple.Create(items[j], errors[j])); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if (stringBuilder.Length > 0) |             return results; | ||||||
|             { |  | ||||||
|                 return new OperResult(stringBuilder.ToString()); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 return OperResult.CreateSuccessResult(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         catch (COMException ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|         finally |         finally | ||||||
|         { |         { | ||||||
| @@ -299,7 +285,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|     /// 组读取 |     /// 组读取 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <exception cref="ExternalException"></exception> |     /// <exception cref="ExternalException"></exception> | ||||||
|     internal OperResult ReadAsync() |     internal void ReadAsync() | ||||||
|     { |     { | ||||||
|         IntPtr pErrors = IntPtr.Zero; |         IntPtr pErrors = IntPtr.Zero; | ||||||
|         try |         try | ||||||
| @@ -316,16 +302,11 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|                 Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count); |                 Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count); | ||||||
|                 if (PErrors.Any(a => a > 0)) |                 if (PErrors.Any(a => a > 0)) | ||||||
|                 { |                 { | ||||||
|                     return new OperResult("读取错误,错误代码:" + pErrors); |                     throw new("读取错误,错误代码:" + pErrors); | ||||||
|                 } |                 } | ||||||
|                 return OperResult.CreateSuccessResult(); |  | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|                 return new OperResult("连接无效"); |                 throw new("连接无效"); | ||||||
|         } |  | ||||||
|         catch (COMException ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|         finally |         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; |         IntPtr pErrors = IntPtr.Zero; | ||||||
|         int[] errors = new int[items.Length]; |         int[] errors = new int[items.Length]; | ||||||
| @@ -356,59 +337,42 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|             { |             { | ||||||
|                 Marshal.FreeCoTaskMem(pErrors); |                 Marshal.FreeCoTaskMem(pErrors); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
|         StringBuilder stringBuilder = new(); |         List<Tuple<OpcItem, int>> results = new(); | ||||||
|         for (int i = 0; i < errors.Length; i++) |         for (int i = 0; i < errors.Length; i++) | ||||||
|         { |         { | ||||||
|             if (errors[i] != 0) |             if (errors[i] != 0) | ||||||
|             { |             { | ||||||
|                 stringBuilder.AppendLine(items[i].ItemID + "移除失败,错误代码:" + errors[i]); |                 results.Add(Tuple.Create(items[i], errors[i])); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 OpcItems.Remove(items[i]); |                 OpcItems.Remove(items[i]); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (stringBuilder.Length > 0) |         return results; | ||||||
|         { |  | ||||||
|             return new OperResult(stringBuilder.ToString()); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             return OperResult.CreateSuccessResult(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     internal OperResult Write(object[] values, int[] serverHandle, out int[] errors) | 
 | ||||||
|  | 
 | ||||||
|  |     internal List<Tuple<int, int>> Write(object[] values, int[] serverHandle) | ||||||
|     { |     { | ||||||
|         IntPtr pErrors = IntPtr.Zero; |         IntPtr pErrors = IntPtr.Zero; | ||||||
|         errors = new int[values.Length]; |         var errors = new int[values.Length]; | ||||||
|         if (m_Async2IO != null) |         if (m_Async2IO != null) | ||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 m_SyncIO.Write(values.Length, serverHandle, values, out pErrors); |                 m_SyncIO.Write(values.Length, serverHandle, values, out pErrors); | ||||||
|                 Marshal.Copy(pErrors, errors, 0, values.Length); |                 Marshal.Copy(pErrors, errors, 0, values.Length); | ||||||
|                 StringBuilder stringBuilder = new(); |                 List<Tuple<int, int>> results = new(); | ||||||
|                 for (int i = 0; i < errors.Length; i++) |                 for (int i = 0; i < errors.Length; i++) | ||||||
|                 { |                 { | ||||||
|                     if (errors[i] != 0) |                     if (errors[i] != 0) | ||||||
|                     { |                     { | ||||||
|                         stringBuilder.AppendLine("写入错误,错误代码:" + errors[i]); |                         results.Add(Tuple.Create(serverHandle[i], errors[i])); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if (stringBuilder.Length > 0) |                 return results; | ||||||
|                 { |  | ||||||
|                     return new(stringBuilder.ToString()); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     return OperResult.CreateSuccessResult(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             catch (COMException ex) |  | ||||||
|             { |  | ||||||
|                 return new OperResult(ex); |  | ||||||
|             } |             } | ||||||
|             finally |             finally | ||||||
|             { |             { | ||||||
| @@ -419,7 +383,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|             return new OperResult("连接无效"); |             throw new("连接无效"); | ||||||
|     } |     } | ||||||
|     protected virtual void Dispose(bool disposing) |     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 pRequestedUpdateRate = IntPtr.Zero; | ||||||
|         IntPtr hClientGroup = IntPtr.Zero; |         IntPtr hClientGroup = IntPtr.Zero; | ||||||
| @@ -484,11 +448,6 @@ internal class OpcGroup : IOPCDataCallback, IDisposable | |||||||
|                                         pDeadband, |                                         pDeadband, | ||||||
|                                         pLCID, |                                         pLCID, | ||||||
|                                         hClientGroup); |                                         hClientGroup); | ||||||
|             return OperResult.CreateSuccessResult(); |  | ||||||
|         } |  | ||||||
|         catch (COMException ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|         finally |         finally | ||||||
|         { |         { | ||||||
| @@ -10,8 +10,6 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | ||||||
| @@ -51,58 +49,51 @@ internal class OpcServer : IDisposable | |||||||
|         GC.SuppressFinalize(this); |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     internal OperResult<OpcGroup> AddGroup(string groupName) |     internal OpcGroup AddGroup(string groupName) | ||||||
|     { |     { | ||||||
|         return AddGroup(groupName, true, 1000, 0); |         return AddGroup(groupName, true, 1000, 0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <returns></returns> |     /// <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) |         if (null == m_OpcServer || IsConnected == false) | ||||||
|             return new OperResult<OpcGroup>("未初始化连接!"); |             throw new("未初始化连接!"); | ||||||
|         OpcGroup group = new(groupName, active, reqUpdateRate, deadBand); |         OpcGroup group = new(groupName, active, reqUpdateRate, deadBand); | ||||||
|         Guid riid = typeof(IOPCItemMgt).GUID; |         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.InitIoInterfaces(group.groupPointer); | ||||||
|                 group.IsActive ? 1 : 0,//IsActive |             OpcGroups.Add(group); | ||||||
|                 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); |  | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         else | ||||||
|         { |         { | ||||||
|             return new OperResult<OpcGroup>(ex); |             throw new("添加OPC组错误,OPC服务器返回null"); | ||||||
|         } |         } | ||||||
|  |         return group; | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取节点 |     /// 获取节点 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal OperResult<List<BrowseElement>> Browse(string itemId = null) |     internal List<BrowseElement> Browse(string itemId = null) | ||||||
|     { |     { | ||||||
|         lock (this) |         lock (this) | ||||||
|         { |         { | ||||||
|             if (null == m_OpcServer || IsConnected == false) |             if (null == m_OpcServer || IsConnected == false) | ||||||
|                 return new OperResult<List<BrowseElement>>("未初始化连接!"); |                 throw new("未初始化连接!"); | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|             var count = 0; |             var count = 0; | ||||||
|             var moreElements = 0; |             var moreElements = 0; | ||||||
| @@ -118,108 +109,107 @@ internal class OpcServer : IDisposable | |||||||
|                            new PropertyID(6), |                            new PropertyID(6), | ||||||
|                            new PropertyID(101), |                            new PropertyID(101), | ||||||
|                          }; |                          }; | ||||||
|             try |  | ||||||
|             { |  | ||||||
| 
 | 
 | ||||||
|                 var server = m_OpcServer as IOPCBrowse; | 
 | ||||||
|                 server.Browse( |             var server = m_OpcServer as IOPCBrowse; | ||||||
|                          string.IsNullOrEmpty(itemId) ? "" : itemId, |             server.Browse( | ||||||
|                      ref pContinuationPoint, |                      string.IsNullOrEmpty(itemId) ? "" : itemId, | ||||||
|                      int.MaxValue, |                  ref pContinuationPoint, | ||||||
|                         OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL, |                  int.MaxValue, | ||||||
|                           "", |                     OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL, | ||||||
|                          "", |                       "", | ||||||
|                          0, |                      "", | ||||||
|                          1, |                      0, | ||||||
|                          filterId.Length, |                      1, | ||||||
|                          Interop.GetPropertyIDs(filterId), |                      filterId.Length, | ||||||
|                      out moreElements, |                      Interop.GetPropertyIDs(filterId), | ||||||
|                      out count, |                  out moreElements, | ||||||
|                      out pElements); |                  out count, | ||||||
|             } |                  out pElements); | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 return new OperResult<List<BrowseElement>>(ex); |  | ||||||
|             } |  | ||||||
|             BrowseElement[] browseElements = Interop.GetBrowseElements(ref pElements, count, true); |             BrowseElement[] browseElements = Interop.GetBrowseElements(ref pElements, count, true); | ||||||
|             string stringUni = Marshal.PtrToStringUni(pContinuationPoint); |             string stringUni = Marshal.PtrToStringUni(pContinuationPoint); | ||||||
|             Marshal.FreeCoTaskMem(pContinuationPoint); |             Marshal.FreeCoTaskMem(pContinuationPoint); | ||||||
|             this.ProcessResults(browseElements, filterId); |             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)) |         if (!string.IsNullOrEmpty(Host) && !string.IsNullOrEmpty(Name)) | ||||||
|         { |         { | ||||||
|             var info = Discovery.OpcDiscovery.GetOpcServer(Name, Host); |             var info = Discovery.OpcDiscovery.GetOpcServer(Name, Host); | ||||||
|             if (!info.IsSuccess) |             object o = Comn.ComInterop.CreateInstance(info.CLSID, Host); | ||||||
|             { |  | ||||||
|                 return info; |  | ||||||
|             } |  | ||||||
|             object o = Comn.ComInterop.CreateInstance(info.Content.CLSID, Host); |  | ||||||
|             if (o == null) |             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; |             m_OpcServer = (IOPCServer)o; | ||||||
|             IsConnected = true; |             IsConnected = true; | ||||||
|             return OperResult.CreateSuccessResult(); |  | ||||||
|         } |         } | ||||||
|         return new("应初始化Host与Name"); |         else | ||||||
|  |             throw new("应初始化Host与Name"); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 服务器状态 |     /// 服务器状态 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     internal OperResult<ServerStatus> GetServerStatus() |     internal ServerStatus GetServerStatus() | ||||||
|     { |     { | ||||||
|         if (null == m_OpcServer || IsConnected == false) |         ServerStatus serverStatus = null; | ||||||
|             return new OperResult<ServerStatus>("未初始化连接!"); |  | ||||||
|         IntPtr statusPtr = IntPtr.Zero; |  | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|  |             if (null == m_OpcServer || IsConnected == false) | ||||||
|  |                 throw new("未初始化连接!"); | ||||||
|  |             IntPtr statusPtr = IntPtr.Zero; | ||||||
|             m_OpcServer?.GetStatus(out statusPtr); |             m_OpcServer?.GetStatus(out statusPtr); | ||||||
|             OPCSERVERSTATUS status; |             OPCSERVERSTATUS status; | ||||||
|             ServerStatus = new ServerStatus(); |  | ||||||
|             if (statusPtr != IntPtr.Zero) |             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)); |                     status = (OPCSERVERSTATUS)o; | ||||||
|                     if (null != o) |                     serverStatus = new(); | ||||||
|                     { |                     serverStatus.Version = status.wMajorVersion.ToString() + "." + status.wMinorVersion.ToString() + "." + status.wBuildNumber.ToString(); | ||||||
|                         status = (OPCSERVERSTATUS)o; |                     serverStatus.ServerState = status.dwServerState; | ||||||
|                         ServerStatus.Version = status.wMajorVersion.ToString() + "." + status.wMinorVersion.ToString() + "." + status.wBuildNumber.ToString(); |                     serverStatus.StartTime = Comn.Convert.FileTimeToDateTime(status.ftStartTime); | ||||||
|                         ServerStatus.ServerState = status.dwServerState; |                     serverStatus.CurrentTime = Comn.Convert.FileTimeToDateTime(status.ftCurrentTime); | ||||||
|                         ServerStatus.StartTime = Comn.Convert.FileTimeToDateTime(status.ftStartTime); |                     serverStatus.LastUpdateTime = Comn.Convert.FileTimeToDateTime(status.ftLastUpdateTime); | ||||||
|                         ServerStatus.CurrentTime = Comn.Convert.FileTimeToDateTime(status.ftCurrentTime); |                     serverStatus.VendorInfo = status.szVendorInfo; | ||||||
|                         ServerStatus.LastUpdateTime = Comn.Convert.FileTimeToDateTime(status.ftLastUpdateTime); |  | ||||||
|                         ServerStatus.VendorInfo = status.szVendorInfo; |  | ||||||
|                     } |  | ||||||
|                     IsConnected = true; |                     IsConnected = true; | ||||||
|                     return OperResult.CreateSuccessResult(ServerStatus); | 
 | ||||||
|  |                     return serverStatus; | ||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 else | ||||||
|                 { |                 { | ||||||
|                     IsConnected = false; |                     IsConnected = false; | ||||||
|                     return new OperResult<ServerStatus>(ex); |                     throw new("未知错误"); | ||||||
|  | 
 | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 IsConnected = false; |                 IsConnected = false; | ||||||
|                 return new OperResult<ServerStatus>("获取状态失败"); |                 throw new("未知错误"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         finally | ||||||
|         { |         { | ||||||
|             IsConnected = false; |             if (serverStatus != null) | ||||||
|             return new OperResult<ServerStatus>(ex); |                 IsConnected = true; | ||||||
|  |             else | ||||||
|  |                 IsConnected = false; | ||||||
|  |             ServerStatus = serverStatus; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     internal void RemoveGroup(OpcGroup group) |     internal void RemoveGroup(OpcGroup group) | ||||||
| @@ -10,15 +10,11 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using Newtonsoft.Json.Linq; |  | ||||||
| 
 |  | ||||||
| using System.Collections; | using System.Collections; | ||||||
| using System.Linq; | using System.Text; | ||||||
| 
 | 
 | ||||||
| using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | ||||||
| 
 | 
 | ||||||
| using TouchSocket.Core; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Foundation.Adapter.OPCDA.Discovery; | namespace ThingsGateway.Foundation.Adapter.OPCDA.Discovery; | ||||||
| /// <summary> | /// <summary> | ||||||
| /// OpcDiscovery | /// OpcDiscovery | ||||||
| @@ -38,58 +34,58 @@ public class OpcDiscovery | |||||||
|     /// <param name="serverName"></param> |     /// <param name="serverName"></param> | ||||||
|     /// <param name="host"></param> |     /// <param name="host"></param> | ||||||
|     /// <returns></returns> |     /// <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 |         try | ||||||
|         { |         { | ||||||
|             if (string.IsNullOrEmpty(serverName)) |             Guid catid = CATID_OPC_DA20; | ||||||
|             { | 
 | ||||||
|                 return new OperResult<ServerInfo>("检索失败,需提供OPCName"); |             //两种方式,兼容国产部分OPCServer不支持IOPCServerList2的情况 | ||||||
|             } |  | ||||||
|             ServerInfo result = null; |  | ||||||
|             ServerInfo[] serverInfos = null; |  | ||||||
|             object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host); |  | ||||||
|             if (o_Server == null) |  | ||||||
|                 return new OperResult<ServerInfo>("检索失败,请检查是否安装OPC Runtime"); |  | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 Guid catid = CATID_OPC_DA20; |                 IOPCServerList2 m_server2 = (IOPCServerList2)o_Server; | ||||||
| 
 |                 GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server2, catid); | ||||||
|                 //两种方式,兼容国产部分OPCServer不支持IOPCServerList2的情况 |                 if (result == null) | ||||||
|                 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) |  | ||||||
|                 { |                 { | ||||||
|                     IOPCServerList m_server = (IOPCServerList)o_Server; |                     IOPCServerList m_server = (IOPCServerList)o_Server; | ||||||
|                     GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid); |                     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); |                 IOPCServerList m_server = (IOPCServerList)o_Server; | ||||||
|                 o_Server = null; |                 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) |     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 Host { get; set; } = string.Empty; | ||||||
|     internal string ProgID { get; set; } = string.Empty; |     internal string ProgID { get; set; } = string.Empty; | ||||||
|     internal string VerIndProgID { 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(); | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -10,9 +10,6 @@ | |||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| #endregion | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Foundation.Adapter.OPCDA; | namespace ThingsGateway.Foundation.Adapter.OPCDA; | ||||||
| 
 | 
 | ||||||
| internal static class CollectionExtensions | internal static class CollectionExtensions | ||||||
| @@ -23,11 +20,30 @@ internal static class CollectionExtensions | |||||||
|     /// <typeparam name="T"></typeparam> |     /// <typeparam name="T"></typeparam> | ||||||
|     /// <param name="this"></param> |     /// <param name="this"></param> | ||||||
|     /// <param name="where"></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()) |         foreach (var obj in @this.Where(where).ToList()) | ||||||
|         { |         { | ||||||
|             @this.Remove(obj); |             @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; | ||||||
| global using System.Collections.Generic; | global using System.Collections.Generic; | ||||||
| global using System.Linq; | global using System.Linq; | ||||||
| global using System.Threading.Tasks; | global using System.Threading; | ||||||
| 
 | 
 | ||||||
| @@ -0,0 +1,457 @@ | |||||||
|  | #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.Runtime.InteropServices; | ||||||
|  | using System.Text; | ||||||
|  | using System.Timers; | ||||||
|  |  | ||||||
|  | using ThingsGateway.Foundation.Adapter.OPCDA.Da; | ||||||
|  | using ThingsGateway.Foundation.Adapter.OPCDA.Rcw; | ||||||
|  |  | ||||||
|  | using Timer = System.Timers.Timer; | ||||||
|  |  | ||||||
|  | //部分非托管交互代码来自https://gitee.com/Zer0Day/opc-client与OPC基金会opcnet库,更改部分逻辑 | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.Foundation.Adapter.OPCDA; | ||||||
|  | /// <summary> | ||||||
|  | /// 订阅变化项 | ||||||
|  | /// </summary> | ||||||
|  | /// <param name="values"></param> | ||||||
|  | public delegate void DataChangedEventHandler(List<ItemReadResult> values); | ||||||
|  | /// <summary> | ||||||
|  | /// OPCDAClient | ||||||
|  | /// </summary> | ||||||
|  | public class OPCDAClient : IDisposable | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// LogAction | ||||||
|  |     /// </summary> | ||||||
|  |     private readonly Action<byte, object, string, Exception> _logAction; | ||||||
|  |  | ||||||
|  |     private readonly object checkLock = new(); | ||||||
|  |  | ||||||
|  |     private Timer checkTimer; | ||||||
|  |  | ||||||
|  |     private int IsExit = 1; | ||||||
|  |     /// <summary> | ||||||
|  |     /// 当前保存的需订阅列表 | ||||||
|  |     /// </summary> | ||||||
|  |     private Dictionary<string, List<OpcItem>> ItemDicts = new(); | ||||||
|  |  | ||||||
|  |     private OpcServer m_server; | ||||||
|  |     private bool publicConnect; | ||||||
|  |     /// <summary> | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     /// </summary> | ||||||
|  |     public OPCDAClient(Action<byte, object, string, Exception> logAction) | ||||||
|  |     { | ||||||
|  | #if (NET6_0_OR_GREATER) | ||||||
|  |         if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||||||
|  |         { | ||||||
|  |             throw new NotSupportedException("不支持非windows系统"); | ||||||
|  |         } | ||||||
|  | #endif | ||||||
|  |         _logAction = logAction; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 数据变化事件 | ||||||
|  |     /// </summary> | ||||||
|  |     public event DataChangedEventHandler DataChangedHandler; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 是否连接成功 | ||||||
|  |     /// </summary> | ||||||
|  |     public bool IsConnected => m_server?.IsConnected == true; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 当前配置 | ||||||
|  |     /// </summary> | ||||||
|  |     public OPCNode OPCNode { get; private set; } | ||||||
|  |     private List<OpcGroup> Groups => m_server.OpcGroups; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 添加节点,需要在连接成功后执行 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="items">组名称/变量节点,注意每次添加的组名称不能相同</param> | ||||||
|  |     public void AddItems(Dictionary<string, List<OpcItem>> items) | ||||||
|  |     { | ||||||
|  |         if (IsExit == 1) throw new("对象已释放"); | ||||||
|  |         foreach (var item in items) | ||||||
|  |         { | ||||||
|  |             if (IsExit == 1) throw new("对象已释放"); | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 var subscription = m_server.AddGroup(item.Key, true, OPCNode.UpdateRate, OPCNode.DeadBand); | ||||||
|  |                 subscription.ActiveSubscribe = OPCNode.ActiveSubscribe; | ||||||
|  |                 subscription.OnDataChanged += Subscription_OnDataChanged; | ||||||
|  |                 subscription.OnReadCompleted += Subscription_OnDataChanged; | ||||||
|  |  | ||||||
|  |                 var result = subscription.AddOpcItem(item.Value.ToArray()); | ||||||
|  |                 StringBuilder stringBuilder = new StringBuilder(); | ||||||
|  |                 if (result.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     foreach (var item1 in result) | ||||||
|  |                     { | ||||||
|  |                         stringBuilder.Append($"{item1.Item1.ItemID}:{item1.Item2}"); | ||||||
|  |                     } | ||||||
|  |                     _logAction?.Invoke(3, this, $"添加变量失败:{stringBuilder}", null); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ItemDicts.AddOrUpdate(item.Key, item.Value.Where(a => !result.Select(b => b.Item1).Contains(a)).ToList()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 _logAction?.Invoke(3, this, $"添加组失败:{ex.Message}", ex); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         for (int i = 0; i < Groups?.Count; i++) | ||||||
|  |         { | ||||||
|  |             var group = Groups[i]; | ||||||
|  |             if (group != null) | ||||||
|  |             { | ||||||
|  |                 if (group.OpcItems.Count == 0) | ||||||
|  |                 { | ||||||
|  |                     ItemDicts.Remove(group.Name); | ||||||
|  |                     m_server.RemoveGroup(group); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 设置节点并保存,每次重连会自动添加节点 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="items"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     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++)); | ||||||
|  |         return ItemDicts; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 连接服务器 | ||||||
|  |     /// </summary> | ||||||
|  |     public void Connect() | ||||||
|  |     { | ||||||
|  |         publicConnect = true; | ||||||
|  |         Interlocked.CompareExchange(ref IsExit, 0, 1); | ||||||
|  |         PrivateConnect(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 断开连接 | ||||||
|  |     /// </summary> | ||||||
|  |     public void Disconnect() | ||||||
|  |     { | ||||||
|  |         Interlocked.CompareExchange(ref IsExit, 1, 0); | ||||||
|  |         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 List<BrowseElement> GetBrowseElements(string itemId = null) | ||||||
|  |     { | ||||||
|  |         return this.m_server?.Browse(itemId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 获取服务状态 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public ServerStatus GetServerStatus() | ||||||
|  |     { | ||||||
|  |         return this.m_server?.GetServerStatus(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 初始化设置 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="node"></param> | ||||||
|  |     public void Init(OPCNode node) | ||||||
|  |     { | ||||||
|  |         if (node != null) | ||||||
|  |             OPCNode = node; | ||||||
|  |         checkTimer?.Stop(); | ||||||
|  |         checkTimer?.Dispose(); | ||||||
|  |         checkTimer = new Timer(OPCNode.CheckRate * 60 * 1000); | ||||||
|  |         checkTimer.Elapsed += CheckTimer_Elapsed; | ||||||
|  |         checkTimer.Start(); | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             m_server?.Dispose(); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             _logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex); | ||||||
|  |         } | ||||||
|  |         m_server = new OpcServer(OPCNode.OPCName, OPCNode.OPCIP); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 按OPC组读取组内变量,结果会在订阅事件中返回 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="groupName">组名称,值为null时读取全部组</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public void ReadItemsWithGroup(string groupName = null) | ||||||
|  |     { | ||||||
|  |         PrivateConnect(); | ||||||
|  |         { | ||||||
|  |             var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups; | ||||||
|  |             foreach (var group in groups) | ||||||
|  |             { | ||||||
|  |                 if (group.OpcItems.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     group.ReadAsync(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 移除节点 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="items"></param> | ||||||
|  |     public void RemoveItems(List<string> items) | ||||||
|  |     { | ||||||
|  |         foreach (var item in items) | ||||||
|  |         { | ||||||
|  |             if (IsExit == 1) return; | ||||||
|  |             var opcGroups = Groups.Where(it => it.OpcItems.Any(a => a.ItemID == item)); | ||||||
|  |             foreach (var opcGroup in opcGroups) | ||||||
|  |             { | ||||||
|  |                 var tag = opcGroup.OpcItems.Where(a => item == a.ItemID); | ||||||
|  |                 var result = opcGroup.RemoveItem(tag.ToArray()); | ||||||
|  |  | ||||||
|  |                 if (opcGroup.OpcItems.Count == 0) | ||||||
|  |                 { | ||||||
|  |                     opcGroup.OnDataChanged -= Subscription_OnDataChanged; | ||||||
|  |                     ItemDicts.Remove(opcGroup.Name); | ||||||
|  |                     m_server.RemoveGroup(opcGroup); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ItemDicts[opcGroup.Name].RemoveWhere(a => tag.Contains(a) && !result.Select(b => b.Item1).Contains(a)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     public override string ToString() | ||||||
|  |     { | ||||||
|  |         return OPCNode?.ToString(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// 批量写入值 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public Dictionary<string, Tuple<bool, string>> WriteItem(Dictionary<string, object> writeInfos) | ||||||
|  |     { | ||||||
|  |         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 | ||||||
|  |             { | ||||||
|  |                 if (item1.Key == null) | ||||||
|  |                 { | ||||||
|  |                     foreach (var item2 in item1) | ||||||
|  |                     { | ||||||
|  |                         results.AddOrUpdate(item2.Key, Tuple.Create(true, $"不存在该变量{item2.Key}")); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     List<int> serverHandles = new(); | ||||||
|  |                     Dictionary<int, OpcItem> handleItems = new(); | ||||||
|  |                     List<object> values = new(); | ||||||
|  |                     foreach (var item2 in item1) | ||||||
|  |                     { | ||||||
|  |                         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 | ||||||
|  |             { | ||||||
|  |                 var timeer = sender as Timer; | ||||||
|  |                 timeer.Enabled = false; | ||||||
|  |                 timeer.Stop(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void PrivateAddItems() | ||||||
|  |     { | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             AddItems(ItemDicts); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             _logAction?.Invoke(3, this, $"添加点位失败:{ex.Message}", ex); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     private void PrivateConnect() | ||||||
|  |     { | ||||||
|  |         lock (this) | ||||||
|  |         { | ||||||
|  |  | ||||||
|  |             if (m_server?.IsConnected == true) | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     var status = m_server.GetServerStatus(); | ||||||
|  |                 } | ||||||
|  |                 catch | ||||||
|  |                 { | ||||||
|  |                     try | ||||||
|  |                     { | ||||||
|  |                         var status1 = m_server.GetServerStatus(); | ||||||
|  |                     } | ||||||
|  |                     catch | ||||||
|  |                     { | ||||||
|  |  | ||||||
|  |                         Init(OPCNode); | ||||||
|  |                         m_server?.Connect(); | ||||||
|  |                         _logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null); | ||||||
|  |                         PrivateAddItems(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 Init(OPCNode); | ||||||
|  |                 m_server?.Connect(); | ||||||
|  |                 _logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null); | ||||||
|  |                 PrivateAddItems(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void PrivateDisconnect() | ||||||
|  |     { | ||||||
|  |         lock (this) | ||||||
|  |         { | ||||||
|  |             if (IsConnected) | ||||||
|  |                 _logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 断开连接", null); | ||||||
|  |             if (checkTimer != null) | ||||||
|  |             { | ||||||
|  |                 checkTimer.Enabled = false; | ||||||
|  |                 checkTimer.Stop(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 m_server?.Dispose(); | ||||||
|  |                 m_server = null; | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 _logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     private void Subscription_OnDataChanged(List<ItemReadResult> values) | ||||||
|  |     { | ||||||
|  |         DataChangedHandler?.Invoke(values); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -21,7 +21,7 @@ public class OPCNode | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 是否订阅 |     /// 是否订阅 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     [Description("ActiveSubscribe")] |     [Description("订阅")] | ||||||
|     public bool ActiveSubscribe { get; set; } = true; |     public bool ActiveSubscribe { get; set; } = true; | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 内部检测重连间隔/min |     /// 内部检测重连间隔/min | ||||||
| @@ -0,0 +1,4 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  | 	 | ||||||
|  | </Project> | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| #region copyright | #region copyright | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
| @@ -34,6 +34,11 @@ public class OPCNode | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [Description("登录密码")] |     [Description("登录密码")] | ||||||
|     public string Password { get; set; } |     public string Password { get; set; } | ||||||
|  |     /// <summary> | ||||||
|  |     /// 检查域 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("检查域")] | ||||||
|  |     public bool CheckDomain { get; set; } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 更新间隔 |     /// 更新间隔 | ||||||
| @@ -65,6 +70,11 @@ public class OPCNode | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     [Description("安全策略")] |     [Description("安全策略")] | ||||||
|     public bool IsUseSecurity { get; set; } = false; |     public bool IsUseSecurity { get; set; } = false; | ||||||
|  |     /// <summary> | ||||||
|  |     /// 加载服务端数据类型 | ||||||
|  |     /// </summary> | ||||||
|  |     [Description("加载服务端数据类型")] | ||||||
|  |     public bool LoadType { get; set; } = true; | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override string ToString() |     public override string ToString() | ||||||
|     { |     { | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| #region copyright | #region copyright | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | //  此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||||
| @@ -17,13 +17,6 @@ using Opc.Ua.Client; | |||||||
| using Opc.Ua.Client.ComplexTypes; | using Opc.Ua.Client.ComplexTypes; | ||||||
| using Opc.Ua.Configuration; | 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库 | //修改自https://github.com/dathlin/OpcUaHelper 与OPC基金会net库 | ||||||
| 
 | 
 | ||||||
| @@ -36,7 +29,7 @@ public delegate void DataChangedEventHandler((VariableNode variableNode, DataVal | |||||||
| /// <summary> | /// <summary> | ||||||
| /// OPCUAClient | /// OPCUAClient | ||||||
| /// </summary> | /// </summary> | ||||||
| public class OPCUAClient : DisposableObject | public class OPCUAClient : IDisposable | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|     #region 属性,变量等 |     #region 属性,变量等 | ||||||
| @@ -55,39 +48,32 @@ public class OPCUAClient : DisposableObject | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public List<string> Variables = new(); |     public List<string> Variables = new(); | ||||||
| 
 | 
 | ||||||
|  |     private readonly Action<byte, object, string, Exception> _logAction; | ||||||
|  | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当前的变量名称/OPC变量节点 |     /// 当前的变量名称/OPC变量节点 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly Dictionary<string, VariableNode> _variableDicts = new(); |     private readonly Dictionary<string, VariableNode> _variableDicts = new(); | ||||||
|     private readonly EasyLock checkLock = new(); |     private readonly object checkLock = new(); | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当前的订阅组,组名称/组 |     /// 当前的订阅组,组名称/组 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private readonly Dictionary<string, Subscription> dic_subscriptions = new(); |     private readonly Dictionary<string, Subscription> dic_subscriptions = new(); | ||||||
| 
 |  | ||||||
|     private readonly ILog Log; |  | ||||||
|     private readonly ApplicationInstance m_application = new(); |     private readonly ApplicationInstance m_application = new(); | ||||||
| 
 | 
 | ||||||
|     private readonly ApplicationConfiguration m_configuration; |     private readonly ApplicationConfiguration m_configuration; | ||||||
| 
 |  | ||||||
|     private SessionReconnectHandler m_reConnectHandler; |     private SessionReconnectHandler m_reConnectHandler; | ||||||
|  | 
 | ||||||
|     private ISession m_session; |     private ISession m_session; | ||||||
|  | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 默认的构造函数,实例化一个新的OPC UA类 |     /// 默认的构造函数,实例化一个新的OPC UA类 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public OPCUAClient(ILog log) |     public OPCUAClient(Action<byte, object, string, Exception> log) | ||||||
|     { |     { | ||||||
|         Log = log; |         _logAction = log; | ||||||
|         var certificateValidator = new CertificateValidator(); |         var certificateValidator = new CertificateValidator(); | ||||||
|         certificateValidator.CertificateValidation += CertificateValidation; |         certificateValidator.CertificateValidation += CertificateValidation; | ||||||
|         SecurityConfiguration securityConfigurationcv = new() |  | ||||||
|         { |  | ||||||
|             UseValidatedCertificates = true, |  | ||||||
|             AutoAcceptUntrustedCertificates = true,//自动接受证书 |  | ||||||
|             RejectSHA1SignedCertificates = false, |  | ||||||
|             MinimumCertificateKeySize = 1024, |  | ||||||
|         }; |  | ||||||
|         certificateValidator.Update(securityConfigurationcv); |  | ||||||
| 
 | 
 | ||||||
|         // 构建应用程序配置 |         // 构建应用程序配置 | ||||||
|         m_configuration = new ApplicationConfiguration |         m_configuration = new ApplicationConfiguration | ||||||
| @@ -109,7 +95,8 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
|             SecurityConfiguration = new SecurityConfiguration |             SecurityConfiguration = new SecurityConfiguration | ||||||
|             { |             { | ||||||
|                 AutoAcceptUntrustedCertificates = true, |                 UseValidatedCertificates = true, | ||||||
|  |                 AutoAcceptUntrustedCertificates = true,//自动接受证书 | ||||||
|                 RejectSHA1SignedCertificates = false, |                 RejectSHA1SignedCertificates = false, | ||||||
|                 MinimumCertificateKeySize = 1024, |                 MinimumCertificateKeySize = 1024, | ||||||
|                 SuppressNonceValidationErrors = true, |                 SuppressNonceValidationErrors = true, | ||||||
| @@ -169,6 +156,8 @@ public class OPCUAClient : DisposableObject | |||||||
|             DisableHiResClock = true |             DisableHiResClock = true | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  |         certificateValidator.Update(m_configuration); | ||||||
|  | 
 | ||||||
|         m_configuration.Validate(ApplicationType.Client); |         m_configuration.Validate(ApplicationType.Client); | ||||||
|         m_application.ApplicationConfiguration = m_configuration; |         m_application.ApplicationConfiguration = m_configuration; | ||||||
| 
 | 
 | ||||||
| @@ -195,6 +184,10 @@ public class OPCUAClient : DisposableObject | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public string OPCUAName { get; set; } = "ThingsGateway"; |     public string OPCUAName { get; set; } = "ThingsGateway"; | ||||||
| 
 | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// SessionReconnectHandler | ||||||
|  |     /// </summary> | ||||||
|  |     public SessionReconnectHandler ReConnectHandler => m_reConnectHandler; | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 当前活动会话。 |     /// 当前活动会话。 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -207,7 +200,7 @@ public class OPCUAClient : DisposableObject | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 新增订阅,需要指定订阅组名称,订阅的tag名数组 |     /// 新增订阅,需要指定订阅组名称,订阅的tag名数组 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async Task AddSubscriptionAsync(string subscriptionName, string[] items) |     public async Task AddSubscriptionAsync(string subscriptionName, string[] items, bool loadType = true) | ||||||
|     { |     { | ||||||
|         Subscription m_subscription = new(m_session.DefaultSubscription) |         Subscription m_subscription = new(m_session.DefaultSubscription) | ||||||
|         { |         { | ||||||
| @@ -220,27 +213,25 @@ public class OPCUAClient : DisposableObject | |||||||
|             DisplayName = subscriptionName |             DisplayName = subscriptionName | ||||||
|         }; |         }; | ||||||
|         List<MonitoredItem> monitoredItems = new(); |         List<MonitoredItem> monitoredItems = new(); | ||||||
|  |         var variableNodes = loadType ? await ReadNodesAsync(items) : null; | ||||||
|         for (int i = 0; i < items.Length; i++) |         for (int i = 0; i < items.Length; i++) | ||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 var variableNode = (VariableNode)ReadNode(items[i], false); |  | ||||||
|                 var item = new MonitoredItem |                 var item = new MonitoredItem | ||||||
|                 { |                 { | ||||||
|                     StartNodeId = variableNode.NodeId, |                     StartNodeId = loadType ? variableNodes[i].NodeId : items[i], | ||||||
|                     AttributeId = Attributes.Value, |                     AttributeId = Attributes.Value, | ||||||
|                     DisplayName = items[i], |                     DisplayName = items[i], | ||||||
|                     Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue }, |                     Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue }, | ||||||
|                     SamplingInterval = OPCNode?.UpdateRate ?? 1000, |                     SamplingInterval = OPCNode?.UpdateRate ?? 1000, | ||||||
|                 }; |                 }; | ||||||
|                 await typeSystem.LoadType(variableNode.DataType, true, true); |  | ||||||
| 
 |  | ||||||
|                 item.Notification += Callback; |                 item.Notification += Callback; | ||||||
|                 monitoredItems.Add(item); |                 monitoredItems.Add(item); | ||||||
|             } |             } | ||||||
|             catch (Exception ex) |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 Log.Error($"初始化{items[i]}变量订阅失败" + ex.Message); |                 _logAction?.Invoke(3, this, $"初始化{items[i]}变量订阅失败", ex); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         m_subscription.AddItems(monitoredItems); |         m_subscription.AddItems(monitoredItems); | ||||||
| @@ -253,10 +244,12 @@ public class OPCUAClient : DisposableObject | |||||||
|         } |         } | ||||||
|         m_subscription.ApplyChanges(); |         m_subscription.ApplyChanges(); | ||||||
| 
 | 
 | ||||||
|         var iserror = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)); |         var isError = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)); | ||||||
|         if (iserror) |         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()); |             _logAction?.Invoke(3, this, $"创建以下变量订阅失败:{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()}", null);
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         lock (dic_subscriptions) |         lock (dic_subscriptions) | ||||||
| @@ -266,7 +259,7 @@ public class OPCUAClient : DisposableObject | |||||||
|                 // remove |                 // remove | ||||||
|                 dic_subscriptions[subscriptionName].Delete(true); |                 dic_subscriptions[subscriptionName].Delete(true); | ||||||
|                 m_session.RemoveSubscription(dic_subscriptions[subscriptionName]); |                 m_session.RemoveSubscription(dic_subscriptions[subscriptionName]); | ||||||
|                 dic_subscriptions[subscriptionName].SafeDispose(); |                 try { dic_subscriptions[subscriptionName].Dispose(); } catch { } | ||||||
|                 dic_subscriptions[subscriptionName] = m_subscription; |                 dic_subscriptions[subscriptionName] = m_subscription; | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
| @@ -287,7 +280,8 @@ public class OPCUAClient : DisposableObject | |||||||
|             { |             { | ||||||
|                 item.Value.Delete(true); |                 item.Value.Delete(true); | ||||||
|                 m_session.RemoveSubscription(item.Value); |                 m_session.RemoveSubscription(item.Value); | ||||||
|                 item.Value.SafeDispose(); |                 try { item.Value.Dispose(); } catch { } | ||||||
|  | 
 | ||||||
|             } |             } | ||||||
|             dic_subscriptions.Clear(); |             dic_subscriptions.Clear(); | ||||||
|         } |         } | ||||||
| @@ -306,7 +300,7 @@ public class OPCUAClient : DisposableObject | |||||||
|                 // remove |                 // remove | ||||||
|                 dic_subscriptions[subscriptionName].Delete(true); |                 dic_subscriptions[subscriptionName].Delete(true); | ||||||
|                 m_session.RemoveSubscription(dic_subscriptions[subscriptionName]); |                 m_session.RemoveSubscription(dic_subscriptions[subscriptionName]); | ||||||
|                 dic_subscriptions[subscriptionName].SafeDispose(); |                 try { dic_subscriptions[subscriptionName].Dispose(); } catch { } | ||||||
|                 dic_subscriptions.RemoveWhere(a => a.Key == subscriptionName); |                 dic_subscriptions.RemoveWhere(a => a.Key == subscriptionName); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -317,7 +311,7 @@ public class OPCUAClient : DisposableObject | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             VariableNode variableNode = (VariableNode)ReadNode(monitoreditem.StartNodeId.ToString(), false); |             var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false); | ||||||
|             foreach (var value in monitoreditem.DequeueValues()) |             foreach (var value in monitoreditem.DequeueValues()) | ||||||
|             { |             { | ||||||
|                 if (value.Value != null) |                 if (value.Value != null) | ||||||
| @@ -325,7 +319,8 @@ public class OPCUAClient : DisposableObject | |||||||
|                     var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value); |                     var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value); | ||||||
|                     if (data == null && value.Value != null) |                     if (data == null && value.Value != null) | ||||||
|                     { |                     { | ||||||
|                         Log.Warning($"{monitoreditem.StartNodeId}转换出错,原始值String为{value.Value}"); |                         _logAction?.Invoke(3, this, $"{monitoreditem.StartNodeId}转换出错,原始值String为{value.Value}", null); | ||||||
|  |                         var data1 = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value); | ||||||
|                     } |                     } | ||||||
|                     DataChangedHandler?.Invoke((variableNode, value, data)); |                     DataChangedHandler?.Invoke((variableNode, value, data)); | ||||||
|                 } |                 } | ||||||
| @@ -339,7 +334,7 @@ public class OPCUAClient : DisposableObject | |||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             Log.Warning($"{monitoreditem.StartNodeId}订阅事件出错:{ex.Message}"); |             _logAction?.Invoke(3, this, $"{monitoreditem.StartNodeId}订阅处理错误", ex); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| @@ -421,9 +416,9 @@ public class OPCUAClient : DisposableObject | |||||||
|     /// <param name="end">结束时间</param> |     /// <param name="end">结束时间</param> | ||||||
|     /// <param name="count">读取的个数</param> |     /// <param name="count">读取的个数</param> | ||||||
|     /// <param name="containBound">是否包含边界</param> |     /// <param name="containBound">是否包含边界</param> | ||||||
|     /// <param name="token">token</param> |     /// <param name="cancellationToken">cancellationToken</param> | ||||||
|     /// <returns>读取的数据列表</returns> |     /// <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() |         HistoryReadValueId m_nodeToContinue = new() | ||||||
|         { |         { | ||||||
| @@ -450,7 +445,7 @@ public class OPCUAClient : DisposableObject | |||||||
|              TimestampsToReturn.Both, |              TimestampsToReturn.Both, | ||||||
|              false, |              false, | ||||||
|              nodesToRead, |              nodesToRead, | ||||||
|              token); |              cancellationToken); | ||||||
|         var results = result.Results; |         var results = result.Results; | ||||||
|         var diagnosticInfos = result.DiagnosticInfos; |         var diagnosticInfos = result.DiagnosticInfos; | ||||||
|         ClientBase.ValidateResponse(results, nodesToRead); |         ClientBase.ValidateResponse(results, nodesToRead); | ||||||
| @@ -469,12 +464,15 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     #region 连接 |     #region 连接 | ||||||
|  |     private ComplexTypeSystem typeSystem; | ||||||
|  | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 连接到服务器 |     /// 连接到服务器 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async Task ConnectAsync() |     public async Task ConnectAsync() | ||||||
|     { |     { | ||||||
|         await ConnectAsync(OPCNode.OPCUrl); |         await ConnectAsync(OPCNode.OPCUrl); | ||||||
|  |         _logAction?.Invoke(1, this, $"连接成功", null); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -486,20 +484,70 @@ public class OPCUAClient : DisposableObject | |||||||
|         // disconnect any existing session. |         // disconnect any existing session. | ||||||
|         if (m_session != null) |         if (m_session != null) | ||||||
|         { |         { | ||||||
|             Log.Debug("断开连接"); |             _logAction?.Invoke(1, this, $"主动断开连接", null); | ||||||
|             m_session = null; |             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); | ||||||
|  | 
 | ||||||
|  |         //如果是订阅模式,连接时添加订阅组 | ||||||
|  |         if (OPCNode.ActiveSubscribe) | ||||||
|  |             await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray(), OPCNode.LoadType); | ||||||
|  |         return m_session; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     private void PrivateDisconnect() |     private void PrivateDisconnect() | ||||||
|     { |     { | ||||||
|         // stop any reconnect operation. |  | ||||||
|         if (m_reConnectHandler != null) |         if (m_reConnectHandler != null) | ||||||
|         { |         { | ||||||
|             m_reConnectHandler.SafeDispose(); |             try { m_reConnectHandler.Dispose(); } catch { } | ||||||
|             m_reConnectHandler = null; |             m_reConnectHandler = null; | ||||||
|         } |         } | ||||||
|         m_session?.Close(10000); |         if (m_session != null) | ||||||
|  |         { | ||||||
|  |             m_session.KeepAlive -= Session_KeepAlive; | ||||||
|  |             m_session.Close(10000); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| @@ -507,23 +555,21 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
|     #region 读取/写入 |     #region 读取/写入 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从服务器读取值 |     /// 从服务器读取值 | ||||||
|     /// </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; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 异步写opc标签 |     /// 异步写opc标签 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async Task<Dictionary<string, OperResult>> WriteNodeAsync(Dictionary<string, JToken> writeInfoLists, CancellationToken token = default) |     public async Task<Dictionary<string, Tuple<bool, string>>> WriteNodeAsync(Dictionary<string, JToken> writeInfoLists, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         Dictionary<string, OperResult> results = new(); |         Dictionary<string, Tuple<bool, string>> results = new(); | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             WriteValueCollection valuesToWrite = new(); |             WriteValueCollection valuesToWrite = new(); | ||||||
| @@ -534,8 +580,7 @@ public class OPCUAClient : DisposableObject | |||||||
|                     NodeId = new NodeId(item.Key), |                     NodeId = new NodeId(item.Key), | ||||||
|                     AttributeId = Attributes.Value, |                     AttributeId = Attributes.Value, | ||||||
|                 }; |                 }; | ||||||
|                 var variableNode = (VariableNode)ReadNode(item.Key, false); |                 var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken); | ||||||
|                 await typeSystem.LoadType(variableNode.DataType, true, true); |  | ||||||
|                 var dataValue = JsonUtils.Decode( |                 var dataValue = JsonUtils.Decode( | ||||||
|                     m_session.MessageContext, |                     m_session.MessageContext, | ||||||
|                     variableNode.DataType, |                     variableNode.DataType, | ||||||
| @@ -551,7 +596,7 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
|             var result = await m_session.WriteAsync( |             var result = await m_session.WriteAsync( | ||||||
|      requestHeader: null, |      requestHeader: null, | ||||||
|      nodesToWrite: valuesToWrite, token); |      nodesToWrite: valuesToWrite, cancellationToken); | ||||||
| 
 | 
 | ||||||
|             ClientBase.ValidateResponse(result.Results, valuesToWrite); |             ClientBase.ValidateResponse(result.Results, valuesToWrite); | ||||||
|             ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, valuesToWrite); |             ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, valuesToWrite); | ||||||
| @@ -561,10 +606,10 @@ public class OPCUAClient : DisposableObject | |||||||
|             for (int i = 0; i < keys.Count; i++) |             for (int i = 0; i < keys.Count; i++) | ||||||
|             { |             { | ||||||
|                 if (!StatusCode.IsGood(result.Results[i])) |                 if (!StatusCode.IsGood(result.Results[i])) | ||||||
|                     results.Add(keys[i], new(result.Results[i].ToString())); |                     results.Add(keys[i], Tuple.Create(true, result.Results[i].ToString())); | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     results.Add(keys[i], OperResult.CreateSuccessResult()); |                     results.Add(keys[i], Tuple.Create(false, "成功")); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @@ -575,7 +620,7 @@ public class OPCUAClient : DisposableObject | |||||||
|             var keys = writeInfoLists.Keys.ToList(); |             var keys = writeInfoLists.Keys.ToList(); | ||||||
|             foreach (var item in keys) |             foreach (var item in keys) | ||||||
|             { |             { | ||||||
|                 results.Add(item, new(ex)); |                 results.Add(item, Tuple.Create(true, ex.Message)); | ||||||
|             } |             } | ||||||
|             return results; |             return results; | ||||||
|         } |         } | ||||||
| @@ -585,7 +630,7 @@ public class OPCUAClient : DisposableObject | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从服务器读取值 |     /// 从服务器读取值 | ||||||
|     /// </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) |         if (m_session == null) | ||||||
|         { |         { | ||||||
| @@ -594,8 +639,6 @@ public class OPCUAClient : DisposableObject | |||||||
|         ReadValueIdCollection nodesToRead = new(); |         ReadValueIdCollection nodesToRead = new(); | ||||||
|         for (int i = 0; i < nodeIds.Length; i++) |         for (int i = 0; i < nodeIds.Length; i++) | ||||||
|         { |         { | ||||||
|             var variableNode = (VariableNode)ReadNode(nodeIds[i].ToString(), false); |  | ||||||
|             await typeSystem.LoadType(variableNode.DataType, true, true); |  | ||||||
|             nodesToRead.Add(new ReadValueId() |             nodesToRead.Add(new ReadValueId() | ||||||
|             { |             { | ||||||
|                 NodeId = nodeIds[i], |                 NodeId = nodeIds[i], | ||||||
| @@ -609,7 +652,7 @@ public class OPCUAClient : DisposableObject | |||||||
|              0, |              0, | ||||||
|              TimestampsToReturn.Neither, |              TimestampsToReturn.Neither, | ||||||
|              nodesToRead, |              nodesToRead, | ||||||
|              token); |              cancellationToken); | ||||||
|         var results = result.Results; |         var results = result.Results; | ||||||
|         var diagnosticInfos = result.DiagnosticInfos; |         var diagnosticInfos = result.DiagnosticInfos; | ||||||
|         ClientBase.ValidateResponse(results, nodesToRead); |         ClientBase.ValidateResponse(results, nodesToRead); | ||||||
| @@ -617,7 +660,7 @@ public class OPCUAClient : DisposableObject | |||||||
|         List<(string, DataValue, JToken)> jTokens = new(); |         List<(string, DataValue, JToken)> jTokens = new(); | ||||||
|         for (int i = 0; i < results.Count; i++) |         for (int i = 0; i < results.Count; i++) | ||||||
|         { |         { | ||||||
|             var variableNode = (VariableNode)ReadNode(nodeIds[i].ToString(), false); |             var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken); | ||||||
|             var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable); |             var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable); | ||||||
|             var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value); |             var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value); | ||||||
|             jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken)); |             jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken)); | ||||||
| @@ -628,7 +671,7 @@ public class OPCUAClient : DisposableObject | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 从服务器或缓存读取节点 |     /// 从服务器或缓存读取节点 | ||||||
|     /// </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) |         if (!isOnlyServer) | ||||||
|         { |         { | ||||||
| @@ -637,21 +680,73 @@ public class OPCUAClient : DisposableObject | |||||||
|                 return value; |                 return value; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         NodeId nodeToRead = new(nodeIdStr); |         NodeId nodeToRead = new(nodeIdStr); | ||||||
|         var node = m_session.ReadNode(nodeToRead); |         var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken); | ||||||
|         _variableDicts.AddOrUpdate(nodeIdStr, (VariableNode)node); |         if (OPCNode.LoadType) | ||||||
|  |             await typeSystem.LoadType(node.DataType); | ||||||
|  |         _variableDicts.AddOrUpdate(nodeIdStr, node); | ||||||
|         return node; |         return node; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /// <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 | ||||||
|  |             { | ||||||
|  |                 _logAction?.Invoke(3, this, $"获取服务器节点信息失败{nodes.Item2[i]}", null); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return nodes.Item1.ToList(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     #endregion |     #endregion | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     #region 特性 |     #region 特性 | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 读取一个节点的所有属性 |     /// 读取一个节点的所有属性 | ||||||
|     /// </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(); |         BrowseDescriptionCollection nodesToBrowse = new(); | ||||||
|         ReadValueIdCollection nodesToRead = new(); |         ReadValueIdCollection nodesToRead = new(); | ||||||
| @@ -674,16 +769,16 @@ public class OPCUAClient : DisposableObject | |||||||
|         }; |         }; | ||||||
|         nodesToBrowse.Add(nodeToBrowse); |         nodesToBrowse.Add(nodeToBrowse); | ||||||
| 
 | 
 | ||||||
|         var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, token); |         var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken); | ||||||
|         var result2 = result1.Copy<List<OPCNodeAttribute>>(); | 
 | ||||||
|         result2.Content = result1.Content?.Values?.FirstOrDefault()?.ToList(); |         var result2 = result1.Values.FirstOrDefault(); | ||||||
|         return result2; |         return result2; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 读取节点的所有属性 |     /// 读取节点的所有属性 | ||||||
|     /// </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(); |         BrowseDescriptionCollection nodesToBrowse = new(); | ||||||
|         ReadValueIdCollection nodesToRead = new(); |         ReadValueIdCollection nodesToRead = new(); | ||||||
| @@ -713,7 +808,7 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, token); |         return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -879,11 +974,9 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override void Dispose(bool disposing) |     public void Dispose() | ||||||
|     { |     { | ||||||
|         Disconnect(); |         Disconnect(); | ||||||
|         checkLock.SafeDispose(); |  | ||||||
|         base.Dispose(disposing); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #region 私有方法 |     #region 私有方法 | ||||||
| @@ -897,66 +990,17 @@ public class OPCUAClient : DisposableObject | |||||||
|         else |         else | ||||||
|             throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo)); |             throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo)); | ||||||
|     } |     } | ||||||
|     private ComplexTypeSystem typeSystem; |  | ||||||
|     /// <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(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>()); |  | ||||||
| 
 |  | ||||||
|         typeSystem = new ComplexTypeSystem(m_session); |  | ||||||
|         Log.Debug("连接成功"); |  | ||||||
| 
 |  | ||||||
|         m_session.KeepAliveInterval = OPCNode.KeepAliveInterval == 0 ? 60000 : OPCNode.KeepAliveInterval; |  | ||||||
|         m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive); |  | ||||||
| 
 |  | ||||||
|         //如果是订阅模式,连接时添加订阅组 |  | ||||||
|         if (OPCNode.ActiveSubscribe) |  | ||||||
|             await AddSubscriptionAsync(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; |         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) |         if (references == null) | ||||||
|         { |         { | ||||||
|             return new OperResult<Dictionary<string, List<OPCNodeAttribute>>>("浏览失败"); |             throw new("浏览失败"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (int ii = 0; ii < references.Count; ii++) |         for (int ii = 0; ii < references.Count; ii++) | ||||||
| @@ -978,7 +1022,7 @@ public class OPCUAClient : DisposableObject | |||||||
|             null, |             null, | ||||||
|             0, |             0, | ||||||
|             TimestampsToReturn.Neither, |             TimestampsToReturn.Neither, | ||||||
|             nodesToRead, token); |             nodesToRead, cancellationToken); | ||||||
| 
 | 
 | ||||||
|         ClientBase.ValidateResponse(result.Results, nodesToRead); |         ClientBase.ValidateResponse(result.Results, nodesToRead); | ||||||
|         ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, nodesToRead); |         ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, nodesToRead); | ||||||
| @@ -1034,8 +1078,7 @@ public class OPCUAClient : DisposableObject | |||||||
|                 nodeAttributes.Add(nodeToRead.NodeId.ToString(), new() { item }); |                 nodeAttributes.Add(nodeToRead.NodeId.ToString(), new() { item }); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |         return nodeAttributes; | ||||||
|         return OperResult.CreateSuccessResult(nodeAttributes); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -1049,17 +1092,15 @@ public class OPCUAClient : DisposableObject | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         m_session = m_reConnectHandler.Session; |         m_session = m_reConnectHandler.Session; | ||||||
|         m_reConnectHandler.SafeDispose(); |  | ||||||
|         m_reConnectHandler = null; |         m_reConnectHandler = null; | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void Session_KeepAlive(ISession session, KeepAliveEventArgs e) |     private void Session_KeepAlive(ISession session, KeepAliveEventArgs e) | ||||||
|     { |     { | ||||||
|         if (checkLock.IsWaitting) { return; } |         lock (checkLock) | ||||||
|         checkLock.Wait(); |  | ||||||
|         try |  | ||||||
|         { |         { | ||||||
|  | 
 | ||||||
|             if (!Object.ReferenceEquals(session, m_session)) |             if (!Object.ReferenceEquals(session, m_session)) | ||||||
|             { |             { | ||||||
|                 return; |                 return; | ||||||
| @@ -1067,7 +1108,7 @@ public class OPCUAClient : DisposableObject | |||||||
| 
 | 
 | ||||||
|             if (ServiceResult.IsBad(e.Status)) |             if (ServiceResult.IsBad(e.Status)) | ||||||
|             { |             { | ||||||
|                 Log.Warning($"心跳检测错误:{e.Status}"); |                 _logAction?.Invoke(3, this, $"心跳检测错误:{e.Status}", null); | ||||||
| 
 | 
 | ||||||
|                 if (m_reConnectHandler == null) |                 if (m_reConnectHandler == null) | ||||||
|                 { |                 { | ||||||
| @@ -1078,11 +1119,7 @@ public class OPCUAClient : DisposableObject | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             Log.Trace($"心跳检测正常 [{session.Endpoint.EndpointUrl}]"); | 
 | ||||||
|         } |  | ||||||
|         finally |  | ||||||
|         { |  | ||||||
|             checkLock.Release(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -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 | #endregion | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 |  | ||||||
| namespace ThingsGateway.Foundation.Adapter.OPCUA; | namespace ThingsGateway.Foundation.Adapter.OPCUA; | ||||||
| 
 | 
 | ||||||
| internal static class CollectionExtensions | internal static class CollectionExtensions | ||||||
| @@ -24,7 +20,7 @@ internal static class CollectionExtensions | |||||||
|     /// <typeparam name="T"></typeparam> |     /// <typeparam name="T"></typeparam> | ||||||
|     /// <param name="this"></param> |     /// <param name="this"></param> | ||||||
|     /// <param name="where"></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()) |         foreach (var obj in @this.Where(where).ToList()) | ||||||
|         { |         { | ||||||
| @@ -39,7 +35,7 @@ internal static class CollectionExtensions | |||||||
|     /// <param name="source"></param> |     /// <param name="source"></param> | ||||||
|     /// <param name="selector"></param> |     /// <param name="selector"></param> | ||||||
|     /// <returns></returns> |     /// <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)); |         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; | ||||||
| using Opc.Ua.Client; | using Opc.Ua.Client; | ||||||
| 
 | 
 | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation.Adapter.OPCUA; | namespace ThingsGateway.Foundation.Adapter.OPCUA; | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -150,16 +147,17 @@ public class FormUtils | |||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 浏览地址空间 |     /// 浏览地址空间 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="session"></param> |     /// <param name="session"></param> | ||||||
|     /// <param name="nodesToBrowse"></param> |     /// <param name="nodesToBrowse"></param> | ||||||
|     /// <param name="throwOnError"></param> |     /// <param name="throwOnError"></param> | ||||||
|     /// <param name="token"></param> |     /// <param name="cancellationToken"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     /// <exception cref="ServiceResultException"></exception> |     /// <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 |         try | ||||||
|         { |         { | ||||||
| @@ -174,16 +172,23 @@ public class FormUtils | |||||||
|                         null, |                         null, | ||||||
|                         null, |                         null, | ||||||
|                         0, |                         0, | ||||||
|                         nodesToBrowse, token); |                         nodesToBrowse, cancellationToken); | ||||||
|                 var results = result.Results; |                 var results = result.Results; | ||||||
|                 var diagnosticInfos = result.DiagnosticInfos; |                 var diagnosticInfos = result.DiagnosticInfos; | ||||||
|                 ClientBase.ValidateResponse(results, nodesToBrowse); |                 ClientBase.ValidateResponse(results, nodesToBrowse); | ||||||
|                 ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); |                 ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); | ||||||
| 
 | 
 | ||||||
|                 ByteStringCollection continuationPoints = new(); |                 var continuationPoints = PrepareBrowseNext(result.Results); | ||||||
| 
 | 
 | ||||||
|                 for (int ii = 0; ii < nodesToBrowse.Count; ii++) |                 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. |                     // check for error. | ||||||
|                     if (StatusCode.IsBad(results[ii].StatusCode)) |                     if (StatusCode.IsBad(results[ii].StatusCode)) | ||||||
|                     { |                     { | ||||||
| @@ -198,33 +203,22 @@ public class FormUtils | |||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     // check if all references have been fetched. | 
 | ||||||
|                     if (results[ii].References.Count == 0) |  | ||||||
|                     { |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|                     // save results. |                     // save results. | ||||||
|                     references.AddRange(results[ii].References); |                     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. |                     // continue browse operation. | ||||||
|                     var nextResult = await session.BrowseNextAsync( |                     var nextResult = await session.BrowseNextAsync( | ||||||
|                           null, |                           null, | ||||||
|                           true, |                           false, | ||||||
|                           continuationPoints |                           continuationPoints | ||||||
|                           , token); |                           , cancellationToken); | ||||||
|                     results = nextResult.Results; |                     results = nextResult.Results; | ||||||
|                     diagnosticInfos = nextResult.DiagnosticInfos; |                     diagnosticInfos = nextResult.DiagnosticInfos; | ||||||
|                     ClientBase.ValidateResponse(results, continuationPoints); |                     ClientBase.ValidateResponse(results, continuationPoints); | ||||||
| @@ -232,6 +226,11 @@ public class FormUtils | |||||||
| 
 | 
 | ||||||
|                     for (int ii = 0; ii < continuationPoints.Count; ii++) |                     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. |                         // check for error. | ||||||
|                         if (StatusCode.IsBad(results[ii].StatusCode)) |                         if (StatusCode.IsBad(results[ii].StatusCode)) | ||||||
| @@ -239,24 +238,16 @@ public class FormUtils | |||||||
|                             continue; |                             continue; | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         // check if all references have been fetched. | 
 | ||||||
|                         if (results[ii].References.Count == 0) |  | ||||||
|                         { |  | ||||||
|                             continue; |  | ||||||
|                         } |  | ||||||
| 
 | 
 | ||||||
|                         // save results. |                         // save results. | ||||||
|                         references.AddRange(results[ii].References); |                         references.AddRange(results[ii].References); | ||||||
| 
 | 
 | ||||||
|                         // check for continuation point. | 
 | ||||||
|                         if (results[ii].ContinuationPoint != null) |  | ||||||
|                         { |  | ||||||
|                             revisedContiuationPoints.Add(results[ii].ContinuationPoint); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     // check if browsing must continue; |                     // check if browsing must continue; | ||||||
|                     revisedContiuationPoints = continuationPoints; |                     continuationPoints = PrepareBrowseNext(nextResult.Results); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // check if unprocessed results exist. |                 // check if unprocessed results exist. | ||||||
| @@ -283,10 +274,10 @@ public class FormUtils | |||||||
|     /// <param name="session"></param> |     /// <param name="session"></param> | ||||||
|     /// <param name="nodeToBrowse"></param> |     /// <param name="nodeToBrowse"></param> | ||||||
|     /// <param name="throwOnError"></param> |     /// <param name="throwOnError"></param> | ||||||
|     /// <param name="token"></param> |     /// <param name="cancellationToken"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     /// <exception cref="ServiceResultException"></exception> |     /// <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 |         try | ||||||
|         { |         { | ||||||
| @@ -305,7 +296,7 @@ public class FormUtils | |||||||
|                   null, |                   null, | ||||||
|                   null, |                   null, | ||||||
|                   0, |                   0, | ||||||
|                   nodesToBrowse, token); |                   nodesToBrowse, cancellationToken); | ||||||
|             var results = result.Results; |             var results = result.Results; | ||||||
|             var diagnosticInfos = result.DiagnosticInfos; |             var diagnosticInfos = result.DiagnosticInfos; | ||||||
|             ClientBase.ValidateResponse(results, nodesToBrowse); |             ClientBase.ValidateResponse(results, nodesToBrowse); | ||||||
| @@ -340,7 +331,7 @@ public class FormUtils | |||||||
|                 var nextResult = await session.BrowseNextAsync( |                 var nextResult = await session.BrowseNextAsync( | ||||||
|                       null, |                       null, | ||||||
|                       false, |                       false, | ||||||
|                       continuationPoints, token); |                       continuationPoints, cancellationToken); | ||||||
|                 results = nextResult.Results; |                 results = nextResult.Results; | ||||||
|                 diagnosticInfos = nextResult.DiagnosticInfos; |                 diagnosticInfos = nextResult.DiagnosticInfos; | ||||||
|                 ClientBase.ValidateResponse(results, continuationPoints); |                 ClientBase.ValidateResponse(results, continuationPoints); | ||||||
| @@ -813,7 +804,7 @@ public class FormUtils | |||||||
|     public static async Task<List<NodeId>> TranslateBrowsePaths( |     public static async Task<List<NodeId>> TranslateBrowsePaths( | ||||||
|         ISession session, |         ISession session, | ||||||
|         NodeId startNodeId, |         NodeId startNodeId, | ||||||
|         NamespaceTable namespacesUris, CancellationToken token, |         NamespaceTable namespacesUris, CancellationToken cancellationToken, | ||||||
|         params string[] relativePaths) |         params string[] relativePaths) | ||||||
|     { |     { | ||||||
|         // build the list of browse paths to follow by parsing the relative paths. |         // 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( |         var result = await session.TranslateBrowsePathsToNodeIdsAsync( | ||||||
|             null, |             null, | ||||||
|             browsePaths, |             browsePaths, | ||||||
|             token); |             cancellationToken); | ||||||
|         BrowsePathResultCollection results = result.Results; |         BrowsePathResultCollection results = result.Results; | ||||||
|         DiagnosticInfoCollection diagnosticInfos = result.DiagnosticInfos; |         DiagnosticInfoCollection diagnosticInfos = result.DiagnosticInfos; | ||||||
|         // ensure that the server returned valid results. |         // ensure that the server returned valid results. | ||||||
| @@ -1115,4 +1106,23 @@ public class FormUtils | |||||||
|             _ => valueRank.ToString(), |             _ => 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
											
										
									
								
							| @@ -17,7 +17,7 @@ using Opc.Ua; | |||||||
| 
 | 
 | ||||||
| using System.Collections; | using System.Collections; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Text; | ||||||
| using System.Xml; | using System.Xml; | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation.Adapter.OPCUA; | namespace ThingsGateway.Foundation.Adapter.OPCUA; | ||||||
| @@ -70,7 +70,7 @@ public static class JsonUtils | |||||||
|                         TypeId = new { Id = dataTypeId.Identifier, Namespace = dataTypeId.NamespaceIndex }, |                         TypeId = new { Id = dataTypeId.Identifier, Namespace = dataTypeId.NamespaceIndex }, | ||||||
|                         Body = json |                         Body = json | ||||||
|                     } |                     } | ||||||
|                 }.ToJson(); |                 }.ToJsonString(); | ||||||
|                 break; |                 break; | ||||||
|             case BuiltInType.Variant: |             case BuiltInType.Variant: | ||||||
|                 var type = TypeInfo.GetDataTypeId(GetSystemType(json.Type)); |                 var type = TypeInfo.GetDataTypeId(GetSystemType(json.Type)); | ||||||
| @@ -82,13 +82,13 @@ public static class JsonUtils | |||||||
|                         Body = json |                         Body = json | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                 }.ToJson(); |                 }.ToJsonString(); | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 newData = new |                 newData = new | ||||||
|                 { |                 { | ||||||
|                     Value = json |                     Value = json | ||||||
|                 }.ToJson(); |                 }.ToJsonString(); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -123,7 +123,7 @@ public static class JsonUtils | |||||||
|                     case BuiltInType.UInt64: { return decoder.ReadUInt64(fieldName); } |                     case BuiltInType.UInt64: { return decoder.ReadUInt64(fieldName); } | ||||||
|                     case BuiltInType.Float: { return decoder.ReadFloat(fieldName); } |                     case BuiltInType.Float: { return decoder.ReadFloat(fieldName); } | ||||||
|                     case BuiltInType.Double: { return decoder.ReadDouble(fieldName); } |                     case BuiltInType.Double: { return decoder.ReadDouble(fieldName); } | ||||||
|                     case BuiltInType.String: { return decoder.ReadField(fieldName, out var token) ? token?.ToString() : null; } |                     case BuiltInType.String: { return decoder.ReadField(fieldName, out var cancellationToken) ? cancellationToken?.ToString() : null; } | ||||||
|                     case BuiltInType.DateTime: { return decoder.ReadDateTime(fieldName); } |                     case BuiltInType.DateTime: { return decoder.ReadDateTime(fieldName); } | ||||||
|                     case BuiltInType.Guid: { return decoder.ReadGuid(fieldName); } |                     case BuiltInType.Guid: { return decoder.ReadGuid(fieldName); } | ||||||
|                     case BuiltInType.ByteString: { return decoder.ReadByteString(fieldName); } |                     case BuiltInType.ByteString: { return decoder.ReadByteString(fieldName); } | ||||||
| @@ -163,7 +163,7 @@ public static class JsonUtils | |||||||
|     /// <param name="type"></param> |     /// <param name="type"></param> | ||||||
|     /// <param name="value"></param> |     /// <param name="value"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static JToken Encode( |     internal static JToken Encode( | ||||||
|         IServiceMessageContext Context, |         IServiceMessageContext Context, | ||||||
|         BuiltInType type, |         BuiltInType type, | ||||||
|         object value |         object value | ||||||
| @@ -172,7 +172,7 @@ public static class JsonUtils | |||||||
|         //对于Integer,Int64,Number等会转化为string JValue! |         //对于Integer,Int64,Number等会转化为string JValue! | ||||||
| 
 | 
 | ||||||
|         using var encoder = CreateEncoder(Context, null, false); |         using var encoder = CreateEncoder(Context, null, false); | ||||||
|         Encode(encoder, type, "Value", value, null); |         Encode(encoder, type, "Value", value); | ||||||
|         var textbuffer = encoder.CloseAndReturnText(); |         var textbuffer = encoder.CloseAndReturnText(); | ||||||
|         using var stringReader = new StringReader(textbuffer); |         using var stringReader = new StringReader(textbuffer); | ||||||
|         using var jsonReader = new JsonTextReader(stringReader); |         using var jsonReader = new JsonTextReader(stringReader); | ||||||
| @@ -180,7 +180,28 @@ public static class JsonUtils | |||||||
|         return jToken["Value"]; |         return jToken["Value"]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static void Encode(JsonEncoder encoder, BuiltInType builtInType, string fieldName, object value, ByteBlock byteBlock) |     /// <summary> | ||||||
|  |     /// CreateEncoder | ||||||
|  |     /// </summary> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     private static OPCUAJsonEncoder CreateEncoder( | ||||||
|  |         IServiceMessageContext context, | ||||||
|  |         Stream stream, | ||||||
|  |         bool useReversibleEncoding = false, | ||||||
|  |         bool topLevelIsArray = false, | ||||||
|  |         bool includeDefaultValues = true, | ||||||
|  |         bool includeDefaultNumbers = true | ||||||
|  |         ) | ||||||
|  |     { | ||||||
|  | 
 | ||||||
|  |         return new OPCUAJsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream) | ||||||
|  |         { | ||||||
|  |             IncludeDefaultValues = includeDefaultValues, | ||||||
|  |             IncludeDefaultNumberValues = includeDefaultNumbers | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static void Encode(OPCUAJsonEncoder encoder, BuiltInType builtInType, string fieldName, object value) | ||||||
|     { |     { | ||||||
|         bool isArray = (value?.GetType().IsArray ?? false) && (builtInType != BuiltInType.ByteString); |         bool isArray = (value?.GetType().IsArray ?? false) && (builtInType != BuiltInType.ByteString); | ||||||
|         bool isCollection = (value is IList) && (builtInType != BuiltInType.ByteString); |         bool isCollection = (value is IList) && (builtInType != BuiltInType.ByteString); | ||||||
| @@ -337,26 +358,6 @@ public static class JsonUtils | |||||||
|             encoder.WriteArray(fieldName, c, c.Rank, builtInType); |             encoder.WriteArray(fieldName, c, c.Rank, builtInType); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// <summary> |  | ||||||
|     /// CreateEncoder |  | ||||||
|     /// </summary> |  | ||||||
|     /// <returns></returns> |  | ||||||
|     private static JsonEncoder CreateEncoder( |  | ||||||
|         IServiceMessageContext context, |  | ||||||
|         Stream stream, |  | ||||||
|         bool useReversibleEncoding = false, |  | ||||||
|         bool topLevelIsArray = false, |  | ||||||
|         bool includeDefaultValues = true, |  | ||||||
|         bool includeDefaultNumbers = true |  | ||||||
|         ) |  | ||||||
|     { |  | ||||||
| 
 |  | ||||||
|         return new JsonEncoder(context, useReversibleEncoding, topLevelIsArray, stream) |  | ||||||
|         { |  | ||||||
|             IncludeDefaultValues = includeDefaultValues, |  | ||||||
|             IncludeDefaultNumberValues = includeDefaultNumbers |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|     #endregion |     #endregion | ||||||
| 
 | 
 | ||||||
|     #region json |     #region json | ||||||
| @@ -366,7 +367,7 @@ public static class JsonUtils | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="jToken"></param> |     /// <param name="jToken"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static int CalculateActualValueRank(this JToken jToken) |     internal static int CalculateActualValueRank(this JToken jToken) | ||||||
|     { |     { | ||||||
|         if (jToken.Type != JTokenType.Array) |         if (jToken.Type != JTokenType.Array) | ||||||
|             return -1; |             return -1; | ||||||
| @@ -381,12 +382,6 @@ public static class JsonUtils | |||||||
|         } |         } | ||||||
|         return numDimensions; |         return numDimensions; | ||||||
|     } |     } | ||||||
|     private static JTokenType GetElementsType(this JToken[] jTokens) |  | ||||||
|     { |  | ||||||
|         if (!jTokens.ElementsHasSameType()) |  | ||||||
|             throw new Exception("The array sent must have the same type of element in each dimension"); |  | ||||||
|         return jTokens.First().Type; |  | ||||||
|     } |  | ||||||
|     private static bool ElementsHasSameType(this JToken[] jTokens) |     private static bool ElementsHasSameType(this JToken[] jTokens) | ||||||
|     { |     { | ||||||
|         var checkType = jTokens[0].Type == JTokenType.Integer ? JTokenType.Float : jTokens[0].Type; |         var checkType = jTokens[0].Type == JTokenType.Integer ? JTokenType.Float : jTokens[0].Type; | ||||||
| @@ -394,6 +389,13 @@ public static class JsonUtils | |||||||
|             .Select(x => (x.Type == JTokenType.Integer) ? JTokenType.Float : x.Type) |             .Select(x => (x.Type == JTokenType.Integer) ? JTokenType.Float : x.Type) | ||||||
|             .All(t => t == checkType); |             .All(t => t == checkType); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     private static JTokenType GetElementsType(this JToken[] jTokens) | ||||||
|  |     { | ||||||
|  |         if (!jTokens.ElementsHasSameType()) | ||||||
|  |             throw new Exception("The array sent must have the same type of element in each dimension"); | ||||||
|  |         return jTokens.First().Type; | ||||||
|  |     } | ||||||
|     private static Type GetSystemType(JTokenType jsonType) |     private static Type GetSystemType(JTokenType jsonType) | ||||||
|     { |     { | ||||||
|         return jsonType switch |         return jsonType switch | ||||||
| @@ -421,4 +423,112 @@ public static class JsonUtils | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #endregion |     #endregion | ||||||
|  | 
 | ||||||
|  |     #region Json序列化和反序列化 | ||||||
|  |     /// <summary> | ||||||
|  |     /// 从字符串到json | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="json"></param> | ||||||
|  |     /// <param name="type"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static object FromJsonString(this string json, Type type) | ||||||
|  |     { | ||||||
|  |         return Newtonsoft.Json.JsonConvert.DeserializeObject(json, type); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// 从字符串到json | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T"></typeparam> | ||||||
|  |     /// <param name="json"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static T FromJsonString<T>(this string json) | ||||||
|  |     { | ||||||
|  |         return (T)FromJsonString(json, typeof(T)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// Json反序列化 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">反序列化类型</typeparam> | ||||||
|  |     /// <param name="datas">数据</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static T JsonDeserializeFromBytes<T>(byte[] datas) | ||||||
|  |     { | ||||||
|  |         return (T)JsonDeserializeFromBytes(datas, typeof(T)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// Json反序列化 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="datas"></param> | ||||||
|  |     /// <param name="type"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static object JsonDeserializeFromBytes(byte[] datas, Type type) | ||||||
|  |     { | ||||||
|  |         return FromJsonString(Encoding.UTF8.GetString(datas), type); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// Json反序列化 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">反序列化类型</typeparam> | ||||||
|  |     /// <param name="path">文件路径</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static T JsonDeserializeFromFile<T>(string path) | ||||||
|  |     { | ||||||
|  |         return JsonDeserializeFromString<T>(File.ReadAllText(path)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// Json反序列化 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">类型</typeparam> | ||||||
|  |     /// <param name="json">json字符串</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static T JsonDeserializeFromString<T>(string json) | ||||||
|  |     { | ||||||
|  |         return FromJsonString<T>(json); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// Json序列化数据对象 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="obj">数据对象</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static byte[] JsonSerializeToBytes(object obj) | ||||||
|  |     { | ||||||
|  |         return Encoding.UTF8.GetBytes(ToJsonString(obj)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// Json序列化至文件 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="obj"></param> | ||||||
|  |     /// <param name="path"></param> | ||||||
|  |     internal static void JsonSerializeToFile(object obj, string path) | ||||||
|  |     { | ||||||
|  |         using (var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) | ||||||
|  |         { | ||||||
|  |             var date = JsonSerializeToBytes(obj); | ||||||
|  |             fileStream.Write(date, 0, date.Length); | ||||||
|  |             fileStream.Close(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     /// 转换为Json | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="item"></param> | ||||||
|  |     /// <param name="isIndented"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     internal static string ToJsonString(this object item, bool isIndented = false) | ||||||
|  |     { | ||||||
|  |         if (isIndented) | ||||||
|  |             return Newtonsoft.Json.JsonConvert.SerializeObject(item, Newtonsoft.Json.Formatting.Indented); | ||||||
|  |         else | ||||||
|  |             return Newtonsoft.Json.JsonConvert.SerializeObject(item); | ||||||
|  |     } | ||||||
|  |     #endregion Json序列化和反序列化 | ||||||
|  | 
 | ||||||
| } | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user