Compare commits
	
		
			190 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 | ||
| 
						 | 
					95d723c578 | ||
| 
						 | 
					2fcd853e86 | ||
| 
						 | 
					07eef7c812 | ||
| 
						 | 
					b01e0757fa | ||
| 
						 | 
					32844a20c6 | ||
| 
						 | 
					5b6532c601 | ||
| 
						 | 
					2c5b4b4027 | ||
| 
						 | 
					72d7ecf195 | ||
| 
						 | 
					2cfa6b4306 | ||
| 
						 | 
					6f6ffde0ab | ||
| 
						 | 
					1694739a16 | ||
| 
						 | 
					95d1e8bfca | ||
| 
						 | 
					60dec08e3c | ||
| 
						 | 
					a99d71be93 | ||
| 
						 | 
					f1331b6a0c | ||
| 
						 | 
					10d66b642b | ||
| 
						 | 
					cd2310e4a8 | ||
| 
						 | 
					1b399cf6b0 | ||
| 
						 | 
					877445bc0a | ||
| 
						 | 
					9a5b345bde | ||
| 
						 | 
					fc9e8ea7b3 | ||
| 
						 | 
					32be6fcfc1 | ||
| 
						 | 
					49847236c2 | ||
| 
						 | 
					d8424443e6 | ||
| 
						 | 
					f3b571ec3f | ||
| 
						 | 
					99318bb5d7 | ||
| 
						 | 
					1aa154c9aa | ||
| 
						 | 
					c65d8a445b | ||
| 
						 | 
					80f4f85570 | ||
| 
						 | 
					5beee43a6b | ||
| 
						 | 
					8d6ae203a0 | ||
| 
						 | 
					4353479a5c | ||
| 
						 | 
					34d7687f9e | ||
| 
						 | 
					b1dc3cf4af | ||
| 
						 | 
					6a58b95933 | ||
| 
						 | 
					d3badfd02b | ||
| 
						 | 
					0098be057b | 
@@ -7,7 +7,7 @@
 | 
			
		||||
 | 
			
		||||
 **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**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 文档
 | 
			
		||||
@@ -22,7 +22,9 @@
 | 
			
		||||
 | 
			
		||||
[ThingsGateway演示地址](http://120.24.62.140:5000/)
 | 
			
		||||
 | 
			
		||||
账户	:  **superAdmin**	密码 : **111111**
 | 
			
		||||
账户	:  **superAdmin**	
 | 
			
		||||
 | 
			
		||||
密码 : **111111**
 | 
			
		||||
 | 
			
		||||
## 赞助
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,149 +1,7 @@
 | 
			
		||||
[*.cs]
 | 
			
		||||
 | 
			
		||||
# CA1822: 将成员标记为 static
 | 
			
		||||
dotnet_diagnostic.CA1822.severity = none
 | 
			
		||||
 | 
			
		||||
# CA1816: Dispose 方法应调用 SuppressFinalize
 | 
			
		||||
dotnet_diagnostic.CA1816.severity = none
 | 
			
		||||
# CA1848: 使用 LoggerMessage 委托
 | 
			
		||||
dotnet_diagnostic.CA1848.severity = none
 | 
			
		||||
 | 
			
		||||
# CA2254: 模板应为静态表达式
 | 
			
		||||
dotnet_diagnostic.CA2254.severity = none
 | 
			
		||||
 | 
			
		||||
[*.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
 | 
			
		||||
dotnet_diagnostic.CA2254.severity = suggestion
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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,4 +12,5 @@
 | 
			
		||||
 | 
			
		||||
global using System;
 | 
			
		||||
 | 
			
		||||
global using TouchSocket.Core;
 | 
			
		||||
global using ThingsGateway.Components;
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using Photino.Blazor;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
internal class Program
 | 
			
		||||
{
 | 
			
		||||
    [STAThread]
 | 
			
		||||
    private static void Main(string[] args)
 | 
			
		||||
    {
 | 
			
		||||
        System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
 | 
			
		||||
 | 
			
		||||
        var appBuilder = PhotinoBlazorAppBuilder.CreateDefault(args);
 | 
			
		||||
 | 
			
		||||
        appBuilder.RootComponents.Add<App>("#app");
 | 
			
		||||
 | 
			
		||||
        appBuilder.Services.ThingsGatewayComponentsConfigureServices();
 | 
			
		||||
        var app = appBuilder.Build();
 | 
			
		||||
        app.MainWindow.SetTitle("ThingsGateway.Foundation.Demo");
 | 
			
		||||
        app.MainWindow.SetIconFile("wwwroot/favicon.ico");
 | 
			
		||||
        AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
 | 
			
		||||
        {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        app.Run();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<OutputType>WinExe</OutputType>
 | 
			
		||||
		<ApplicationIcon>favicon.ico</ApplicationIcon>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <None Remove="favicon.ico" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <Content Include="favicon.ico">
 | 
			
		||||
	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
 | 
			
		||||
	    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 | 
			
		||||
	    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
 | 
			
		||||
	  </Content>
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Photino.Blazor" Version="2.6.0" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <ProjectReference Include="..\ThingsGateway.Foundation.Demo.Rcl\ThingsGateway.Foundation.Demo.Rcl.csproj" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								framework/Demo/ThingsGateway.Foundation.Demo.Photino/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								framework/Demo/ThingsGateway.Foundation.Demo.Photino/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 4.2 KiB  | 
							
								
								
									
										26
									
								
								framework/Demo/ThingsGateway.Foundation.Demo.Rcl/App.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								framework/Demo/ThingsGateway.Foundation.Demo.Rcl/App.razor
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
@*
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
 | 
			
		||||
<Router AppAssembly="@typeof(App).Assembly">
 | 
			
		||||
    <Found Context="routeData">
 | 
			
		||||
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
 | 
			
		||||
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
 | 
			
		||||
    </Found>
 | 
			
		||||
    <NotFound>
 | 
			
		||||
        <LayoutView Layout="@typeof(MainLayout)">
 | 
			
		||||
            <p role="alert">Sorry, there's nothing at this address.</p>
 | 
			
		||||
        </LayoutView>
 | 
			
		||||
    </NotFound>
 | 
			
		||||
</Router>
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,148 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using Microsoft.AspNetCore.Components;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Components;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 调试UI
 | 
			
		||||
/// </summary>
 | 
			
		||||
public abstract class DriverDebugUIBase : ComponentBase, IDisposable
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 日志缓存
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ConcurrentLinkedList<(LogLevel level, string message)> Messages = new();
 | 
			
		||||
 | 
			
		||||
    private PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(1));
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    ~DriverDebugUIBase()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 变量地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public virtual string Address { get; set; } = "40001";
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 长度
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public virtual int Length { get; set; } = 1;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 默认读写设备
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public virtual IReadWrite Plc { get; set; }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 写入值
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public virtual string WriteValue { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 数据类型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected virtual DataTypeEnum DataTypeEnum { get; set; } = DataTypeEnum.Int16;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Inject]
 | 
			
		||||
    public InitTimezone InitTimezone { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public virtual void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        _periodicTimer?.Dispose();
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void LogOut(ThingsGateway.Foundation.Core.LogLevel logLevel, object source, string message, Exception exception)
 | 
			
		||||
    {
 | 
			
		||||
        Messages.Add(((LogLevel)logLevel,
 | 
			
		||||
            $"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - {message} {exception}"));
 | 
			
		||||
        if (Messages.Count > 2500)
 | 
			
		||||
        {
 | 
			
		||||
            Messages.Clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public virtual async Task ReadAsync()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var data = await Plc.ReadAsync(Address, Length, DataTypeEnum);
 | 
			
		||||
            if (data.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                Messages.Add((LogLevel.Information,
 | 
			
		||||
            $"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - 对应类型值:{Environment.NewLine}{data.Content.ToJsonString(true)} "));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                Messages.Add((LogLevel.Warning,
 | 
			
		||||
            $"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - {data.Message}"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Messages.Add((LogLevel.Error,
 | 
			
		||||
            $"{DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(InitTimezone.TimezoneOffset)} - 错误:{ex.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>
 | 
			
		||||
@@ -0,0 +1,106 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public partial class SerialSessionPage : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 日志输出
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Action<LogLevel, object, string, Exception> LogAction;
 | 
			
		||||
 | 
			
		||||
    private TouchSocketConfig config;
 | 
			
		||||
 | 
			
		||||
    private readonly SerialProperty serialProperty = new();
 | 
			
		||||
 | 
			
		||||
    private SerialSession SerialSession { get; set; } = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取对象
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public SerialSession GetSerialSession()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
        var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
        config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
        config.SetSerialProperty(serialProperty);
 | 
			
		||||
        //载入配置
 | 
			
		||||
        SerialSession.Setup(config);
 | 
			
		||||
        return SerialSession;
 | 
			
		||||
    }
 | 
			
		||||
    private async Task ConnectAsync()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            SerialSession.Close();
 | 
			
		||||
            await GetSerialSession().ConnectAsync();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            LogAction?.Invoke(LogLevel.Error, null, null, ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    private void DisConnect()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            SerialSession.Close();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            LogAction?.Invoke(LogLevel.Error, null, null, ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="firstRender"></param>
 | 
			
		||||
    protected override void OnAfterRender(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
            LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
            config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
            SerialSession.Setup(config);
 | 
			
		||||
        }
 | 
			
		||||
        base.OnAfterRender(firstRender);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        SerialSession.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    internal void StateHasChangedAsync()
 | 
			
		||||
    {
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,21 +10,19 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@namespace ThingsGateway.Blazor
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
@using BlazorComponent;
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Web;
 | 
			
		||||
@using System.IO.Ports;
 | 
			
		||||
@using System.Collections.Concurrent;
 | 
			
		||||
@using ThingsGateway.Foundation;
 | 
			
		||||
@using ThingsGateway.Foundation.Serial;
 | 
			
		||||
@using ThingsGateway.Foundation.Core;
 | 
			
		||||
@using Masa.Blazor
 | 
			
		||||
@using TouchSocket.Core;
 | 
			
		||||
@using TouchSocket.Sockets;
 | 
			
		||||
@implements IDisposable
 | 
			
		||||
<MCard Class="pa-4" Flat Elevation="0" Rounded="false">
 | 
			
		||||
    <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start">
 | 
			
		||||
        <MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
 | 
			
		||||
        <MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
 | 
			
		||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
 | 
			
		||||
    <div class="mb-4">通道配置</div>
 | 
			
		||||
    <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
 | 
			
		||||
 | 
			
		||||
        <MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
 | 
			
		||||
        <MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
 | 
			
		||||
 | 
			
		||||
        <MButton Class="ma-1" OnClick=@ConnectAsync Color="primary">
 | 
			
		||||
            连接
 | 
			
		||||
@@ -33,7 +31,4 @@
 | 
			
		||||
            断开
 | 
			
		||||
        </MButton>
 | 
			
		||||
    </MRow>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</MCard>
 | 
			
		||||
@@ -0,0 +1,116 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public partial class TcpClientPage : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 日志输出
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Action<LogLevel, object, string, Exception> LogAction;
 | 
			
		||||
 | 
			
		||||
    private TouchSocketConfig config;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// IP
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private string IP = "127.0.0.1";
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Port
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int Port { get; set; } = 502;
 | 
			
		||||
 | 
			
		||||
    private TcpClient TcpClient { get; set; } = new();
 | 
			
		||||
 | 
			
		||||
    private async Task ConnectAsync()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            TcpClient.Close();
 | 
			
		||||
            await GetTcpClient().ConnectAsync();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            LogAction?.Invoke(LogLevel.Error, null, null, ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    private void DisConnect()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            TcpClient.Close();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            LogAction?.Invoke(LogLevel.Error, null, null, ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取对象
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public TcpClient GetTcpClient()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
        var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
        config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
        config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
 | 
			
		||||
        //载入配置
 | 
			
		||||
        TcpClient.Setup(config);
 | 
			
		||||
        return TcpClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="firstRender"></param>
 | 
			
		||||
    protected override void OnAfterRender(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
            LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
            config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
            config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
 | 
			
		||||
            TcpClient.Setup(config);
 | 
			
		||||
        }
 | 
			
		||||
        base.OnAfterRender(firstRender);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        TcpClient.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal void StateHasChangedAsync()
 | 
			
		||||
    {
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,21 +10,20 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@namespace ThingsGateway.Blazor
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
@using BlazorComponent;
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Web;
 | 
			
		||||
@using System.IO.Ports;
 | 
			
		||||
@using System.Collections.Concurrent;
 | 
			
		||||
@using ThingsGateway.Foundation;
 | 
			
		||||
@using ThingsGateway.Foundation.Serial;
 | 
			
		||||
@using ThingsGateway.Foundation.Core;
 | 
			
		||||
@using Masa.Blazor
 | 
			
		||||
@using TouchSocket.Core;
 | 
			
		||||
@using TouchSocket.Sockets;
 | 
			
		||||
@implements IDisposable
 | 
			
		||||
<MCard Class="pa-4" Flat Elevation="0" Rounded="false">
 | 
			
		||||
    <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start">
 | 
			
		||||
        <MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip />
 | 
			
		||||
        <MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port />
 | 
			
		||||
 | 
			
		||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
 | 
			
		||||
    <div class="mb-4">通道配置</div>
 | 
			
		||||
    <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
 | 
			
		||||
 | 
			
		||||
        <MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip />
 | 
			
		||||
        <MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port />
 | 
			
		||||
 | 
			
		||||
        <MButton Class="ma-1" OnClick=@Connect Color="primary">
 | 
			
		||||
            连接
 | 
			
		||||
@@ -32,8 +31,8 @@
 | 
			
		||||
        <MButton Class="ma-1" OnClick=@DisConnect Color="red">
 | 
			
		||||
            断开
 | 
			
		||||
        </MButton>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </MRow>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</MCard>
 | 
			
		||||
@@ -1,25 +1,22 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
using TouchSocket.Sockets;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Blazor;
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public partial class TcpServerPage
 | 
			
		||||
public partial class TcpServerPage : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 日志输出
 | 
			
		||||
    /// 日志输出
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Action<LogLevel, object, string, Exception> LogAction;
 | 
			
		||||
 | 
			
		||||
@@ -31,11 +28,6 @@ public partial class TcpServerPage
 | 
			
		||||
 | 
			
		||||
    private TcpService TcpServer { get; set; } = new();
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        TcpServer.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    private void Connect()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
@@ -62,38 +54,55 @@ public partial class TcpServerPage
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取对象
 | 
			
		||||
    /// 获取对象
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public TcpService GetTcpServer()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
        var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace });
 | 
			
		||||
        var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
        config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
        config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) });
 | 
			
		||||
        config.SetBufferLength(300);
 | 
			
		||||
        //载入配置
 | 
			
		||||
        //载入配置
 | 
			
		||||
        TcpServer.Setup(config);
 | 
			
		||||
        return TcpServer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    /// <param name="firstRender"></param>
 | 
			
		||||
    protected override void OnAfterRender(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        config?.Dispose();
 | 
			
		||||
        config = new TouchSocketConfig();
 | 
			
		||||
        var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace });
 | 
			
		||||
        config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
        config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) });
 | 
			
		||||
        config.SetBufferLength(300);
 | 
			
		||||
        TcpServer = new TcpService();
 | 
			
		||||
        TcpServer.Setup(config);
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
            LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
            config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
            config.SetListenIPHosts(new IPHost[] { new IPHost(ip + ":" + port) });
 | 
			
		||||
            TcpServer.Setup(config);
 | 
			
		||||
        }
 | 
			
		||||
        base.OnAfterRender(firstRender);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        TcpServer.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    internal void StateHasChangedAsync()
 | 
			
		||||
    {
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,21 +10,20 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@namespace ThingsGateway.Blazor
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
@using BlazorComponent;
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Web;
 | 
			
		||||
@using System.IO.Ports;
 | 
			
		||||
@using System.Collections.Concurrent;
 | 
			
		||||
@using ThingsGateway.Foundation;
 | 
			
		||||
@using ThingsGateway.Foundation.Serial;
 | 
			
		||||
@using ThingsGateway.Foundation.Core;
 | 
			
		||||
@using Masa.Blazor
 | 
			
		||||
@using TouchSocket.Core;
 | 
			
		||||
@using TouchSocket.Sockets;
 | 
			
		||||
@implements IDisposable
 | 
			
		||||
<MCard Class="pa-4" Flat Elevation="0" Rounded="false">
 | 
			
		||||
    <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Start">
 | 
			
		||||
        <MTextField Class="ma-1" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@ip />
 | 
			
		||||
        <MTextField Class="ma-1" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@port />
 | 
			
		||||
 | 
			
		||||
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
 | 
			
		||||
    <div class="mb-4">通道配置</div>
 | 
			
		||||
    <MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
 | 
			
		||||
 | 
			
		||||
        <MTextField Class="ma-1" Style="max-width:100px" Label="IP地址" Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
 | 
			
		||||
        <MTextField Class="ma-1" Style="max-width:100px" Label="端口" Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
 | 
			
		||||
 | 
			
		||||
        <MButton Class="ma-1" OnClick=Connect Color="primary">
 | 
			
		||||
            连接
 | 
			
		||||
@@ -32,8 +31,7 @@
 | 
			
		||||
        <MButton Class="ma-1" OnClick=DisConnect Color="red">
 | 
			
		||||
            断开
 | 
			
		||||
        </MButton>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </MRow>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</MCard>
 | 
			
		||||
@@ -1,41 +1,37 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
using TouchSocket.Sockets;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Blazor;
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public partial class UdpSessionPage : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 日志输出
 | 
			
		||||
    /// 日志输出
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Action<LogLevel, object, string, Exception> LogAction;
 | 
			
		||||
 | 
			
		||||
    private TouchSocketConfig config;
 | 
			
		||||
 | 
			
		||||
    private string ip = "127.0.0.1";
 | 
			
		||||
 | 
			
		||||
    private int port = 502;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// IP
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string IP = "127.0.0.1";
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Port
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int Port = 502;
 | 
			
		||||
 | 
			
		||||
    private UdpSession UdpSession { get; set; } = new();
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        UdpSession.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    private void Connect()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
@@ -62,38 +58,56 @@ public partial class UdpSessionPage : IDisposable
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取对象
 | 
			
		||||
    /// 获取对象
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public UdpSession GetUdpSession()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
        var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace });
 | 
			
		||||
        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)).SetBufferLength(300);
 | 
			
		||||
        config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
 | 
			
		||||
        config.SetBindIPHost(new IPHost(0));
 | 
			
		||||
        //载入配置
 | 
			
		||||
        //载入配置
 | 
			
		||||
        UdpSession.Setup(config);
 | 
			
		||||
        return UdpSession;
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        config ??= new TouchSocketConfig();
 | 
			
		||||
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="firstRender"></param>
 | 
			
		||||
    protected override void OnAfterRender(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            var LogMessage = new LoggerGroup() { LogLevel = LogLevel.Trace };
 | 
			
		||||
            LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = LogLevel.Trace });
 | 
			
		||||
            config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
            config.SetRemoteIPHost(new IPHost(IP + ":" + Port));
 | 
			
		||||
            config.SetBindIPHost(new IPHost(0));
 | 
			
		||||
            UdpSession.Setup(config);
 | 
			
		||||
        }
 | 
			
		||||
        base.OnAfterRender(firstRender);
 | 
			
		||||
    }
 | 
			
		||||
    private void LogOut(LogLevel logLevel, object source, string message, Exception exception) => LogAction?.Invoke(logLevel, source, message, exception);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        config?.Dispose();
 | 
			
		||||
        config = new TouchSocketConfig();
 | 
			
		||||
        var LogMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
 | 
			
		||||
        LogMessage.AddLogger(new EasyLogger(LogOut) { LogLevel = TouchSocket.Core.LogLevel.Trace });
 | 
			
		||||
        config.ConfigureContainer(a => a.RegisterSingleton<ILog>(LogMessage));
 | 
			
		||||
        config.SetRemoteIPHost(new IPHost(ip + ":" + port)).SetBufferLength(300);
 | 
			
		||||
        config.SetBindIPHost(new IPHost(0));
 | 
			
		||||
        UdpSession = new UdpSession();
 | 
			
		||||
        UdpSession.Setup(config);
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
        UdpSession.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    internal void StateHasChangedAsync()
 | 
			
		||||
    {
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
global using System;
 | 
			
		||||
global using System.Linq;
 | 
			
		||||
global using System.Threading;
 | 
			
		||||
global using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
global using TouchSocket.Core;
 | 
			
		||||
global using TouchSocket.Sockets;
 | 
			
		||||
global using ThingsGateway.Components;
 | 
			
		||||
global using ThingsGateway.Foundation.Core;
 | 
			
		||||
global using ThingsGateway.Foundation.Serial;
 | 
			
		||||
global using ThingsGateway.Foundation.Sockets;
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
@*
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@page "/"
 | 
			
		||||
@layout BaseLayout
 | 
			
		||||
@inject NavigationManager NavigationManager
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
@using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
 | 
			
		||||
@code {
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="firstRender"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
			
		||||
    {
 | 
			
		||||
        if (firstRender)
 | 
			
		||||
        {
 | 
			
		||||
            NavigationManager.NavigateTo("index");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await base.OnAfterRenderAsync(firstRender);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,57 @@
 | 
			
		||||
@*
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@page "/index"
 | 
			
		||||
 | 
			
		||||
<div class="ml-2">
 | 
			
		||||
    <div class="my-6 ">
 | 
			
		||||
        <MLabel Class="text-h3" Color="primary">ThingsGateway</MLabel>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <strong class="text--lighten-1 text-h5 my-1">文档</strong>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="my-2 ml-4">
 | 
			
		||||
        <PCopyableText>
 | 
			
		||||
            https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
        </PCopyableText>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <strong class="text--lighten-1 text-h5 my-1">协议</strong>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="my-2 ml-4">
 | 
			
		||||
        <PCopyableText Text="https://gitee.com/diego2098/ThingsGateway/blob/master/LICENSE.zh">
 | 
			
		||||
            Apache-2.0开源协议
 | 
			
		||||
        </PCopyableText>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
        <strong class="text--lighten-1 text-h5 my-1">赞助</strong>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="my-2 ml-4">
 | 
			
		||||
        <PCopyableText Text="https://diego2098.gitee.io/thingsgateway-docs/docs/donate">
 | 
			
		||||
            ThingsGateway赞助途径
 | 
			
		||||
        </PCopyableText>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
        <strong class="text--lighten-1 text-h5 my-1">社区</strong>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="my-2 ml-4">
 | 
			
		||||
        <PCopyableText Text="605534569">
 | 
			
		||||
            QQ群:605534569
 | 
			
		||||
        </PCopyableText>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
@*
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
@inherits LayoutComponentBase
 | 
			
		||||
 | 
			
		||||
<CascadingValue Value="IsMobile" Name="IsMobile">
 | 
			
		||||
    <MApp>
 | 
			
		||||
        <MErrorHandler>
 | 
			
		||||
            @Body
 | 
			
		||||
        </MErrorHandler>
 | 
			
		||||
    </MApp>
 | 
			
		||||
</CascadingValue>
 | 
			
		||||
 | 
			
		||||
@code {
 | 
			
		||||
    public bool IsMobile { get; set; }
 | 
			
		||||
    [Inject]
 | 
			
		||||
    public MasaBlazor MasaBlazor { get; set; }
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
        MasaBlazor.BreakpointChanged += BreakpointOnOnUpdate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void BreakpointOnOnUpdate(object sender, BreakpointChangedEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        IsMobile = MasaBlazor.Breakpoint.Mobile;
 | 
			
		||||
        if (e.MobileChanged)
 | 
			
		||||
        {
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,34 +10,18 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@namespace ThingsGateway.Admin.Blazor
 | 
			
		||||
@using Masa.Blazor.Presets
 | 
			
		||||
@using ThingsGateway.Admin.Blazor.Core
 | 
			
		||||
@namespace ThingsGateway.Foundation.Demo
 | 
			
		||||
@using System.Text;
 | 
			
		||||
@inherits LayoutComponentBase
 | 
			
		||||
@layout BaseLayout
 | 
			
		||||
@if (UserManager.UserId > 0)
 | 
			
		||||
{
 | 
			
		||||
    <SysSignalR></SysSignalR>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
<PPageTabsProvider>
 | 
			
		||||
 | 
			
		||||
    <CascadingValue Value="@this" IsFixed>
 | 
			
		||||
        <CascadingValue Value="@Changed" Name="Changed">
 | 
			
		||||
 | 
			
		||||
            <MNavigationDrawer Color="barcolor" @bind-Value="_drawerOpen" App Width="200">
 | 
			
		||||
                @if (IsMobile)
 | 
			
		||||
                {
 | 
			
		||||
                    <MSystemBar Color="barcolor" Height="@(BlazorResourceConst.PageTabsHeight)">
 | 
			
		||||
                        <MButton Icon OnClick=@(()=> _drawerOpen = !_drawerOpen)>
 | 
			
		||||
                            <MIcon>
 | 
			
		||||
                                mdi-close-thick
 | 
			
		||||
                            </MIcon>
 | 
			
		||||
                        </MButton>
 | 
			
		||||
                        <MSpacer />
 | 
			
		||||
                        <AppbarButtons />
 | 
			
		||||
                    </MSystemBar>
 | 
			
		||||
                }
 | 
			
		||||
                <Logo CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_COPYRIGHT_URL=@CONFIG_COPYRIGHT_URL CONFIG_TITLE=@CONFIG_TITLE HeightInt=@(IsMobile?BlazorResourceConst.AppBarHeight:BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight) />
 | 
			
		||||
                <Logo CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT  CONFIG_TITLE=@CONFIG_TITLE HeightInt=@(IsMobile?BlazorResourceConst.AppBarHeight:BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight) />
 | 
			
		||||
                <AppList ClassString="overflow-y-auto" Routable
 | 
			
		||||
                         StyleString=@($"height: calc(100vh - {BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight}px);")
 | 
			
		||||
                         Items="Navs" />
 | 
			
		||||
@@ -48,18 +32,16 @@
 | 
			
		||||
                <MButton Class="mr-0" Icon Small=IsMobile OnClick=@(() => _drawerOpen = !_drawerOpen)>
 | 
			
		||||
                    <MIcon>mdi-menu</MIcon>
 | 
			
		||||
                </MButton>
 | 
			
		||||
                <AppBarItems CONFIG_COPYRIGHT=@CONFIG_COPYRIGHT CONFIG_COPYRIGHT_URL=@CONFIG_COPYRIGHT_URL CONFIG_TITLE=@CONFIG_TITLE>
 | 
			
		||||
                </AppBarItems>
 | 
			
		||||
 | 
			
		||||
            </MAppBar>
 | 
			
		||||
 | 
			
		||||
            <MMain Style=@($"{(!(IsMobile||_drawerOpen!=true)? "padding-left:200px;":"")}")>
 | 
			
		||||
                <div class="full-width">
 | 
			
		||||
                    <PageTabs @ref="_pageTabs" />
 | 
			
		||||
                    <PageTabs @ref="_pageTabs" PageTabItems="pageTabItems" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <MDivider Center></MDivider>
 | 
			
		||||
                <MCard Flat Class="overflow-y-auto overflow-x-hidden ma-auto pa-0 rounded-0" Style=@($"height: calc(100vh - {BlazorResourceConst.AppBarHeight+BlazorResourceConst.PageTabsHeight+BlazorResourceConst.FooterHeight}px);")>
 | 
			
		||||
                    <PPageContainer PageTabs="@_pageTabs?.PPageTabs" SelfPatterns="@selfPatterns">
 | 
			
		||||
                    <PPageContainer PageTabs="@_pageTabs?.PPageTabs">
 | 
			
		||||
                        @Body
 | 
			
		||||
                    </PPageContainer>
 | 
			
		||||
                </MCard>
 | 
			
		||||
@@ -71,5 +53,25 @@
 | 
			
		||||
    </CascadingValue>
 | 
			
		||||
 | 
			
		||||
</PPageTabsProvider>
 | 
			
		||||
@code {
 | 
			
		||||
    bool Changed { get; set; }
 | 
			
		||||
    private bool? _drawerOpen = true;
 | 
			
		||||
 | 
			
		||||
    private PageTabs _pageTabs;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// IsMobile
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [CascadingParameter(Name = "IsMobile")]
 | 
			
		||||
    public bool IsMobile { get; set; }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@code{
 | 
			
		||||
    private string CONFIG_COPYRIGHT = "Diego";
 | 
			
		||||
    private string CONFIG_COPYRIGHT_URL = "https://gitee.com/diego2098/ThingsGateway";
 | 
			
		||||
    private string CONFIG_TITLE = "ThingsGateway";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,232 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
 | 
			
		||||
//  <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>룩<EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
 | 
			
		||||
//  Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4>鼰<EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
 | 
			
		||||
//  GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQȺ<51><C8BA>605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Demo;
 | 
			
		||||
 | 
			
		||||
public partial class MainLayout
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    private List<NavItem> Navs { get; set; } = new();
 | 
			
		||||
    private List<PageTabItem> pageTabItems { get; set; } = new();
 | 
			
		||||
    protected override void OnInitialized()
 | 
			
		||||
    {
 | 
			
		||||
        var dataString =
 | 
			
		||||
"""
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "Href": "/index",
 | 
			
		||||
    "Title": "<22><>ҳ"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Modbus",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusRtu",
 | 
			
		||||
        "Title": "ModbusRtu"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusTcp",
 | 
			
		||||
        "Title": "ModbusTcp"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusRtuOverTcp",
 | 
			
		||||
        "Title": "ModbusRtuOverTcp"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusRtuOverUdp",
 | 
			
		||||
        "Title": "ModbusRtuOverUdp"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusUdp",
 | 
			
		||||
        "Title": "ModbusUdp"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusTcpDtu",
 | 
			
		||||
        "Title": "ModbusTcpDtu"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusTcpServer",
 | 
			
		||||
        "Title": "ModbusTcpServer"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ModbusSerialServer",
 | 
			
		||||
        "Title": "ModbusSerialServer"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Siemens",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/S7_1500",
 | 
			
		||||
        "Title": "S7_1500"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/S7_1200",
 | 
			
		||||
        "Title": "S7_1200"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/S7_200",
 | 
			
		||||
        "Title": "S7_200"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/S7_200SMART",
 | 
			
		||||
        "Title": "S7_200SMART"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/S7_300",
 | 
			
		||||
        "Title": "S7_400"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/S7_400",
 | 
			
		||||
        "Title": "S7_400"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "DLT645",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/DLT645_2007",
 | 
			
		||||
        "Title": "DLT645_2007"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/DLT645_2007OverTcp",
 | 
			
		||||
        "Title": "DLT645_2007OverTcp"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "OPCDA",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/OPCDAClient",
 | 
			
		||||
        "Title": "OPCDAClient"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "OPCUA",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/OPCUAClient",
 | 
			
		||||
        "Title": "OPCUAClient"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Mqtt",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/MqttClient",
 | 
			
		||||
        "Title": "MqttClient"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
""";
 | 
			
		||||
        Navs = dataString.FromJsonString<List<NavItem>>();
 | 
			
		||||
 | 
			
		||||
#if Pro
 | 
			
		||||
        var dataStringPro =
 | 
			
		||||
"""
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Melsec",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/QnA3E_Binary",
 | 
			
		||||
        "Title": "QnA3E_Binary"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "ABCIP",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/ABCIPTCP",
 | 
			
		||||
        "Title": "ABCIPTCP"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Omron",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/OmronFinsTcp",
 | 
			
		||||
        "Title": "OmronFinsTcp"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/OmronFinsUdp",
 | 
			
		||||
        "Title": "OmronFinsUdp"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Secs",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/SecsTcp",
 | 
			
		||||
        "Title": "SecsTcp"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "TS550",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/TS550",
 | 
			
		||||
        "Title": "TS550"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "Vigor",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/VigorSerial",
 | 
			
		||||
        "Title": "VigorSerial"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/VigorSerialOverTcp",
 | 
			
		||||
        "Title": "VigorSerialOverTcp"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "Title": "GasCustom",
 | 
			
		||||
    "Children": [
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/GasCustomSerial",
 | 
			
		||||
        "Title": "GasCustomSerial"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "Href": "/GasCustomSerialOverTcp",
 | 
			
		||||
        "Title": "GasCustomSerialOverTcp"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
""";
 | 
			
		||||
        Navs.AddRange(dataStringPro.FromJsonString<List<NavItem>>());
 | 
			
		||||
#endif
 | 
			
		||||
        pageTabItems = Navs.PasePageTabItem();
 | 
			
		||||
        base.OnInitialized();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,152 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="'$(SolutionName)'=='ThingsGateway - Pro'">
 | 
			
		||||
		<DefineConstants>Pro</DefineConstants>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	<ItemGroup Condition="'$(SolutionName)'=='ThingsGateway - Pro'">
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Melsec\Page\QnA3E_BinaryDebugPage.razor.cs" Link="Pages\Melsec\QnA3E_BinaryDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Melsec\Page\QnA3E_BinaryDebugPage.razor" Link="Pages\Melsec\QnA3E_BinaryDebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Melsec\ThingsGateway.Foundation.Adapter.Melsec.csproj" />
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.AllenBradleyCip\Page\AllenBradleyCipTcpDebugPage.razor.cs" Link="Pages\ABCIP\AllenBradleyCipTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.AllenBradleyCip\Page\AllenBradleyCipTcpDebugPage.razor" Link="Pages\ABCIP\AllenBradleyCipTcpDebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.AllenBradleyCip\ThingsGateway.Foundation.Adapter.AllenBradleyCip.csproj" />
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsTcpDebugPage.razor.cs" Link="Pages\OmronFins\OmronFinsTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsUdpDebugPage.razor.cs" Link="Pages\OmronFins\OmronFinsUdpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsTcpDebugPage.razor" Link="Pages\OmronFins\OmronFinsTcpDebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Omron\Page\OmronFinsUdpDebugPage.razor" Link="Pages\OmronFins\OmronFinsUdpDebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Omron\ThingsGateway.Foundation.Adapter.Omron.csproj" />
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Secs\Page\SecsHsmsTcpDebugPage.razor.cs" Link="Pages\Secs\SecsHsmsTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Secs\Page\SecsHsmsTcpDebugPage.razor" Link="Pages\Secs\SecsHsmsTcpDebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Secs\ThingsGateway.Foundation.Adapter.Secs.csproj" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.TS550\Page\TS550DebugPage.razor.cs" Link="Pages\TS550\TS550DebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.TS550\Page\TS550DebugPage.razor" Link="Pages\TS550\TS550DebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.TS550\ThingsGateway.Foundation.Adapter.TS550.csproj" />
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialDebugPage.razor.cs" Link="Pages\Vigor\VigorSerialDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialOverTcpDebugPage.razor.cs" Link="Pages\Vigor\VigorSerialOverTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialDebugPage.razor" Link="Pages\Vigor\VigorSerialDebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\PluginPro\ThingsGateway.Plugin.Vigor\Page\VigorSerialOverTcpDebugPage.razor" Link="Pages\Vigor\VigorSerialOverTcpDebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\FoundationPro\ThingsGateway.Foundation.Adapter.Vigor\ThingsGateway.Foundation.Adapter.Vigor.csproj" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialDebugPage.razor.cs" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialOverTcpDebugPage.razor.cs" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialOverTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialDebugPage.razor" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialDebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\PluginProAF2021\ThingsGateway.Plugin.HZW_QTJC_01\Page\HZW_QTJC_01SerialOverTcpDebugPage.razor" Link="Pages\HZW_QTJC_01\HZW_QTJC_01SerialOverTcpDebugPage.razor" />
 | 
			
		||||
		<ProjectReference Include="..\..\PluginProAF2021\ThingsGateway.Foundation.Adapter.HZW_QTJC_01\ThingsGateway.Foundation.Adapter.HZW_QTJC_01.csproj" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007DebugPage.razor.cs" Link="Pages\DLT645\DLT645_2007DebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\MqttRpcNameVaueWithId.cs" Link="Pages\Mqtt\MqttRpcNameVaueWithId.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientDebugPage.razor.cs" Link="Pages\Mqtt\MqttClientDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientPage.razor.cs" Link="Pages\Mqtt\MqttClientPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\PrivateLogger.cs" Link="Pages\Mqtt\PrivateLogger.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcClient.cs" Link="Pages\Mqtt\MqttRpcClient.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcClientExtensions.cs" Link="Pages\Mqtt\MqttRpcClientExtensions.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcTopicPair.cs" Link="Pages\Mqtt\MqttRpcTopicPair.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007DebugPage.razor" Link="Pages\DLT645\DLT645_2007DebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007OverTcpDebugPage.razor.cs" Link="Pages\DLT645\DLT645_2007OverTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007OverTcpDebugPage.razor" Link="Pages\DLT645\DLT645_2007OverTcpDebugPage.razor" />
 | 
			
		||||
 | 
			
		||||
		
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuDebugPage.razor.cs" Link="Pages\Modbus\ModbusRtuDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuDebugPage.razor" Link="Pages\Modbus\ModbusRtuDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverTcpDebugPage.razor.cs" Link="Pages\Modbus\ModbusRtuOverTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverTcpDebugPage.razor" Link="Pages\Modbus\ModbusRtuOverTcpDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverUdpDebugPage.razor.cs" Link="Pages\Modbus\ModbusRtuOverUdpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusRtuOverUdpDebugPage.razor" Link="Pages\Modbus\ModbusRtuOverUdpDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusSerialServerDebugPage.razor.cs" Link="Pages\Modbus\ModbusSerialServerDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusSerialServerDebugPage.razor" Link="Pages\Modbus\ModbusSerialServerDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDebugPage.razor.cs" Link="Pages\Modbus\ModbusTcpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDebugPage.razor" Link="Pages\Modbus\ModbusTcpDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDtuDebugPage.razor.cs" Link="Pages\Modbus\ModbusTcpDtuDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpDtuDebugPage.razor" Link="Pages\Modbus\ModbusTcpDtuDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpServerDebugPage.razor.cs" Link="Pages\Modbus\ModbusTcpServerDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusTcpServerDebugPage.razor" Link="Pages\Modbus\ModbusTcpServerDebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusUdpDebugPage.razor.cs" Link="Pages\Modbus\ModbusUdpDebugPage.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Modbus\Page\ModbusUdpDebugPage.razor" Link="Pages\Modbus\ModbusUdpDebugPage.razor" />
 | 
			
		||||
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientDebugPage.razor.cs" Link="Pages\OPCDA\OPCDAClientDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientPage.razor.cs" Link="Pages\OPCDA\OPCDAClientPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAImportVariable.razor.cs" Link="Pages\OPCDA\OPCDAImportVariable.razor.cs" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientDebugPage.razor" Link="Pages\OPCDA\OPCDAClientDebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAClientPage.razor" Link="Pages\OPCDA\OPCDAClientPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCDA\Page\OPCDAImportVariable.razor" Link="Pages\OPCDA\OPCDAImportVariable.razor" />
 | 
			
		||||
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientDebugPage.razor" Link="Pages\OPCUA\OPCUAClientDebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientPage.razor" Link="Pages\OPCUA\OPCUAClientPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAImportVariable.razor" Link="Pages\OPCUA\OPCUAImportVariable.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientDebugPage.razor.cs" Link="Pages\OPCUA\OPCUAClientDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAClientPage.razor.cs" Link="Pages\OPCUA\OPCUAClientPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.OPCUA\Page\OPCUAImportVariable.razor.cs" Link="Pages\OPCUA\OPCUAImportVariable.razor.cs" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1200DebugPage.razor" Link="Pages\Siemens\S7_1200DebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1500DebugPage.razor" Link="Pages\Siemens\S7_1500DebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200DebugPage.razor" Link="Pages\Siemens\S7_200DebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200SMARTDebugPage.razor" Link="Pages\Siemens\S7_200SMARTDebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_300DebugPage.razor" Link="Pages\Siemens\S7_300DebugPage.razor" />
 | 
			
		||||
		<Content Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_400DebugPage.razor" Link="Pages\Siemens\S7_400DebugPage.razor" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1200DebugPage.razor.cs" Link="Pages\Siemens\S7_1200DebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_1500DebugPage.razor.cs" Link="Pages\Siemens\S7_1500DebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200DebugPage.razor.cs" Link="Pages\Siemens\S7_200DebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_200SMARTDebugPage.razor.cs" Link="Pages\Siemens\S7_200SMARTDebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_300DebugPage.razor.cs" Link="Pages\Siemens\S7_300DebugPage.razor.cs" />
 | 
			
		||||
		<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Siemens\Page\S7_400DebugPage.razor.cs" Link="Pages\Siemens\S7_400DebugPage.razor.cs" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup >
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Adapter.DLT645" Version="*" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Adapter.Modbus" Version="*" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Adapter.OPCDA" Version="*" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Adapter.OPCUA" Version="*" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Adapter.Siemens" Version="*" />
 | 
			
		||||
		<!--<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.DLT645\ThingsGateway.Foundation.Adapter.DLT645.csproj" />
 | 
			
		||||
		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.Modbus\ThingsGateway.Foundation.Adapter.Modbus.csproj" />
 | 
			
		||||
		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.OPCDA\ThingsGateway.Foundation.Adapter.OPCDA.csproj" />
 | 
			
		||||
		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.OPCUA\ThingsGateway.Foundation.Adapter.OPCUA.csproj" />
 | 
			
		||||
		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Adapter.Siemens\ThingsGateway.Foundation.Adapter.Siemens.csproj" />-->
 | 
			
		||||
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
	<ItemGroup >
 | 
			
		||||
		<ProjectReference Include="..\..\Web\ThingsGateway.Components\ThingsGateway.Components.csproj" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<Content Update="wwwroot\**">
 | 
			
		||||
			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
 | 
			
		||||
		</Content>
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
	  <Content Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientDebugPage.razor" Link="Pages\Mqtt\MqttClientDebugPage.razor" />
 | 
			
		||||
	  <Content Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientPage.razor" Link="Pages\Mqtt\MqttClientPage.razor" />
 | 
			
		||||
		<PackageReference Include="MQTTnet" Version="4.3.1.873" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
@*
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
*@
 | 
			
		||||
 | 
			
		||||
@using System.Net.Http
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Forms
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Routing
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Web
 | 
			
		||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
 | 
			
		||||
@using Microsoft.JSInterop
 | 
			
		||||
@using BlazorComponent
 | 
			
		||||
@using Masa.Blazor
 | 
			
		||||
@using Masa.Blazor.Presets
 | 
			
		||||
@using ThingsGateway.Foundation.Core;
 | 
			
		||||
@using ThingsGateway.Components;
 | 
			
		||||
@using ThingsGateway.Core;
 | 
			
		||||
@using System.Net.Http.Json
 | 
			
		||||
@using System.IO;
 | 
			
		||||
@using System.Text.Json;
 | 
			
		||||
@using ThingsGateway.Foundation.Serial;
 | 
			
		||||
@using ThingsGateway.Foundation.Sockets;
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 4.2 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 10 KiB  | 
@@ -0,0 +1,41 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
 | 
			
		||||
    <title>ThingsGateway.Foundation.Demo</title>
 | 
			
		||||
    <base href="/" />
 | 
			
		||||
 | 
			
		||||
    <link rel="icon" href="favicon.ico" type="image/x-icon">
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <link href="_content/Masa.Blazor/css/masa-blazor.min.css" rel="stylesheet" />
 | 
			
		||||
    <link href="_content/ThingsGateway.Components/css/materialdesign/v7.1.96/css/materialdesignicons.min.css" rel="stylesheet">
 | 
			
		||||
    <link href="_content/ThingsGateway.Components/css/material/icons.css" rel="stylesheet">
 | 
			
		||||
    <link href="_content/ThingsGateway.Components/css/fontawesome/v6.4.0/css/all.min.css" rel="stylesheet">
 | 
			
		||||
    <link href="_content/ThingsGateway.Components/style/custom.css" rel="stylesheet">
 | 
			
		||||
    <link href="_content/ThingsGateway.Components/prism/prism-material-dark-for-masa.css" rel="stylesheet">
 | 
			
		||||
    <link href="_content/ThingsGateway.Components/prism/prism-line-highlight.min.css" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
 | 
			
		||||
    <div id="blazor-error-ui">
 | 
			
		||||
        <span>
 | 
			
		||||
            <environment include="Staging,Production">
 | 
			
		||||
                An error has occurred. This application may no longer respond until reloaded.
 | 
			
		||||
            </environment>
 | 
			
		||||
            <environment include="Development">
 | 
			
		||||
                An unhandled exception has occurred. See browser dev tools for details.
 | 
			
		||||
            </environment>
 | 
			
		||||
        </span>
 | 
			
		||||
        <a href="" class="reload">Reload</a>
 | 
			
		||||
        <a class="dismiss">🗙</a>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="_framework/blazor.webview.js" autostart="true"></script>
 | 
			
		||||
    <script src="_content/ThingsGateway.Components/prism/prism.min.js"></script>
 | 
			
		||||
    <script src="_content/BlazorComponent/js/blazor-component.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -1,7 +1,12 @@
 | 
			
		||||
<Project>
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
 | 
			
		||||
		<Version>2.0.5.0</Version>
 | 
			
		||||
		<Version>3.0.0.13</Version>
 | 
			
		||||
		<GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
			
		||||
		<LangVersion>latest</LangVersion>
 | 
			
		||||
		<TargetFrameworks>net45;netstandard2.0;net6.0;net7.0</TargetFrameworks>
 | 
			
		||||
		<Description>
 | 
			
		||||
			ThingsGateway.Foundation是工业设备通讯类库,归属于ThingsGateway边缘网关项目,说明文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
		</Description>
 | 
			
		||||
		<Authors>Diego</Authors>
 | 
			
		||||
		<Product>ThingsGateway</Product>
 | 
			
		||||
		<Copyright>© 2023-present Diego</Copyright>
 | 
			
		||||
@@ -11,38 +16,38 @@
 | 
			
		||||
		<EmbedAllSources>true</EmbedAllSources>
 | 
			
		||||
		<RepositoryType>Gitee</RepositoryType>
 | 
			
		||||
		<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
 | 
			
		||||
		<PackageReadmeFile>./README.md</PackageReadmeFile>
 | 
			
		||||
		<PackageReadmeFile>README.md</PackageReadmeFile>
 | 
			
		||||
		<PackageIcon>icon.png</PackageIcon>
 | 
			
		||||
		<IncludeSymbols>true</IncludeSymbols>
 | 
			
		||||
		<SymbolPackageFormat>snupkg</SymbolPackageFormat>
 | 
			
		||||
		<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
 | 
			
		||||
		<PackageProjectUrl>https://diego2098.gitee.io/thingsgateway-docs/</PackageProjectUrl>
 | 
			
		||||
		<PackageIcon>icon.png</PackageIcon>
 | 
			
		||||
		<PackageTags>ThingsGateway;Diego;dotNET China;Blazor;设备采集;边缘网关</PackageTags>
 | 
			
		||||
		<PackageOutputPath>../../../nupkgs</PackageOutputPath>
 | 
			
		||||
		<SignAssembly>True</SignAssembly>
 | 
			
		||||
		<DelaySign>False</DelaySign>
 | 
			
		||||
		<AssemblyOriginatorKeyFile>..\..\..\..\snks\ThingsGateway.snk</AssemblyOriginatorKeyFile>
 | 
			
		||||
		<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
 | 
			
		||||
		<PackageOutputPath>../../nupkgs</PackageOutputPath>
 | 
			
		||||
		<AssemblyOriginatorKeyFile>../../../snks/ThingsGateway.snk</AssemblyOriginatorKeyFile>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<LangVersion>latest</LangVersion>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<None Include="..\..\..\..\README.md" Pack="true" PackagePath="\" />
 | 
			
		||||
		<None Include="..\..\..\README.md" Pack="true" PackagePath="\" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<None Include="..\..\..\..\icon.png">
 | 
			
		||||
		<None Include="..\..\..\icon.png">
 | 
			
		||||
			<Pack>True</Pack>
 | 
			
		||||
			<PackagePath></PackagePath>
 | 
			
		||||
		</None>
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup Condition="'$(Configuration)' == 'Release'">
 | 
			
		||||
		<DebugSymbols>True</DebugSymbols>
 | 
			
		||||
		<DebugType>Embedded</DebugType>
 | 
			
		||||
		<EmbedAllSources>True</EmbedAllSources>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
	
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -10,66 +10,47 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using BlazorComponent;
 | 
			
		||||
namespace ThingsGateway.Admin.Blazor.Core;
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// ListItem
 | 
			
		||||
/// 控制码
 | 
			
		||||
/// </summary>
 | 
			
		||||
/// <typeparam name="TItem"></typeparam>
 | 
			
		||||
public interface IAppItem<TItem>
 | 
			
		||||
public enum ControlCode : byte
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 子菜单
 | 
			
		||||
    /// 读数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    List<TItem> Children { get; }
 | 
			
		||||
 | 
			
		||||
    Read = 0x11,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 是否启用下划线
 | 
			
		||||
    /// 读后续数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    bool Divider { get; set; }
 | 
			
		||||
 | 
			
		||||
    ReadSub = 0x12,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 菜单头部标题
 | 
			
		||||
    /// 读站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    string Heading { get; }
 | 
			
		||||
 | 
			
		||||
    ReadStation = 0x13,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 链接
 | 
			
		||||
    /// 写数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    string Href { get; set; }
 | 
			
		||||
 | 
			
		||||
    Write = 0x14,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 图标
 | 
			
		||||
    /// 写站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    string Icon { get; set; }
 | 
			
		||||
 | 
			
		||||
    WriteStation = 0x15,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 菜单副标题
 | 
			
		||||
    /// 广播校时
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    string SubTitle { get; set; }
 | 
			
		||||
 | 
			
		||||
    BroadcastTime = 0x08,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 跳转方式
 | 
			
		||||
    /// 冻结
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    string Target { get; set; }
 | 
			
		||||
 | 
			
		||||
    Freeze = 0x16,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 菜单标题
 | 
			
		||||
    /// 更新波特率
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    string Title { get; set; }
 | 
			
		||||
 | 
			
		||||
    WriteBaudRate = 0x17,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 菜单值
 | 
			
		||||
    /// 更新密码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    StringNumber Value { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 是否有子菜单
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    bool HasChildren()
 | 
			
		||||
    {
 | 
			
		||||
        return Children is not null && Children.Any();
 | 
			
		||||
    }
 | 
			
		||||
    WritePassword = 0x18,
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,480 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DLT645_2007
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class DLT645_2007 : ReadWriteDevicesSerialSessionBase
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// DLT645_2007
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="serialSession"></param>
 | 
			
		||||
    public DLT645_2007(SerialSession serialSession) : base(serialSession)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new DLT645_2007BitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 增加FE FE FE FE的报文头部
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("前导符报文头")]
 | 
			
		||||
    public bool EnableFEHead { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 写入需操作员代码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("操作员代码")]
 | 
			
		||||
    public string OperCode { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 写入密码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("写入密码")]
 | 
			
		||||
    public string Password { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通讯地址BCD码,一般应该是12个字符
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("通讯地址")]
 | 
			
		||||
    public string Station { get; set; }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        var str = """
 | 
			
		||||
            查看附带文档或者相关资料,下面列举一下常见的数据标识地址 
 | 
			
		||||
            
 | 
			
		||||
            地址                       说明                    
 | 
			
		||||
            -----------------------------------------
 | 
			
		||||
            02010100    A相电压
 | 
			
		||||
            02020100    A相电流
 | 
			
		||||
            02030000    瞬时总有功功率
 | 
			
		||||
            00000000    (当前)组合有功总电能
 | 
			
		||||
            00010000    (当前)正向有功总电能
 | 
			
		||||
            
 | 
			
		||||
            """;
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + str;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<byte[]>(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<byte[]>(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        var dataHandleAdapter = new DLT645_2007DataHandleAdapter
 | 
			
		||||
        {
 | 
			
		||||
            EnableFEHead = EnableFEHead
 | 
			
		||||
        };
 | 
			
		||||
        SerialSession.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, string value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            Password ??= string.Empty;
 | 
			
		||||
            OperCode ??= string.Empty;
 | 
			
		||||
            if (Password.Length < 8)
 | 
			
		||||
                Password = Password.PadLeft(8, '0');
 | 
			
		||||
            if (OperCode.Length < 8)
 | 
			
		||||
                OperCode = OperCode.PadLeft(8, '0');
 | 
			
		||||
            var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
 | 
			
		||||
            string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, string value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            Password ??= string.Empty;
 | 
			
		||||
            OperCode ??= string.Empty;
 | 
			
		||||
            if (Password.Length < 8)
 | 
			
		||||
                Password = Password.PadLeft(8, '0');
 | 
			
		||||
            if (OperCode.Length < 8)
 | 
			
		||||
                OperCode = OperCode.PadLeft(8, '0');
 | 
			
		||||
            var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
 | 
			
		||||
            string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, uint value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, double value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, float value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, long value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, ulong value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, ushort value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, short value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, int value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #region 其他方法
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 广播校时
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="dateTime"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public OperResult BroadcastTime(DateTime dateTime, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            string str = $"{dateTime.Second:D2}{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}{dateTime.Year % 100:D2}";
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.BroadcastTime, str.ByHexStringToBytes().ToArray(), "999999999999".ByHexStringToBytes());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                SerialSession.Send(commandResult.Content);
 | 
			
		||||
                return OperResult.CreateSuccessResult();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 冻结
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="dateTime"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> FreezeAsync(DateTime dateTime, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            string str = $"{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}";
 | 
			
		||||
            if (Station.IsNullOrEmpty()) Station = string.Empty;
 | 
			
		||||
            if (Station.Length < 12) Station = Station.PadLeft(12, '0');
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.Freeze, str.ByHexStringToBytes().ToArray(), Station.ByHexStringToBytes().Reverse().ToArray());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取通信地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult<string>> ReadDeviceStationAsync(CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.ReadStation, null, "AAAAAAAAAAAA".ByHexStringToBytes());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    var buffer = result1.Content.SelectMiddle(0, 6).BytesAdd(-0x33);
 | 
			
		||||
                    return OperResult.CreateSuccessResult(buffer.Reverse().ToArray().ToHexString());
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult<string>(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<string>(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 修改波特率
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="baudRate"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> WriteBaudRateAsync(int baudRate, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            byte baudRateByte;
 | 
			
		||||
            switch (baudRate)
 | 
			
		||||
            {
 | 
			
		||||
                case 600: baudRateByte = 0x02; break;
 | 
			
		||||
                case 1200: baudRateByte = 0x04; break;
 | 
			
		||||
                case 2400: baudRateByte = 0x08; break;
 | 
			
		||||
                case 4800: baudRateByte = 0x10; break;
 | 
			
		||||
                case 9600: baudRateByte = 0x20; break;
 | 
			
		||||
                case 19200: baudRateByte = 0x40; break;
 | 
			
		||||
                default: return new OperResult<string>($"不支持此波特率:{baudRate}");
 | 
			
		||||
            }
 | 
			
		||||
            if (Station.IsNullOrEmpty()) Station = string.Empty;
 | 
			
		||||
            if (Station.Length < 12) Station = Station.PadLeft(12, '0');
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteBaudRate, new byte[] { baudRateByte }, Station.ByHexStringToBytes().Reverse().ToArray());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 更新通信地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="station"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> WriteDeviceStationAsync(string station, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteStation, station.ByHexStringToBytes().Reverse().ToArray(), "AAAAAAAAAAAA".ByHexStringToBytes());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 修改密码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="level"></param>
 | 
			
		||||
    /// <param name="oldPassword"></param>
 | 
			
		||||
    /// <param name="newPassword"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> WritePasswordAsync(byte level, string oldPassword, string newPassword, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
 | 
			
		||||
            if (Station.IsNullOrEmpty()) Station = string.Empty;
 | 
			
		||||
            if (Station.Length < 12) Station = Station.PadLeft(12, '0');
 | 
			
		||||
            string str = $"04000C{(level + 1):D2}";
 | 
			
		||||
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WritePassword,
 | 
			
		||||
                str.ByHexStringToBytes().Reverse().ToArray()
 | 
			
		||||
                .SpliceArray(oldPassword.ByHexStringToBytes().Reverse().ToArray())
 | 
			
		||||
                .SpliceArray(newPassword.ByHexStringToBytes().Reverse().ToArray())
 | 
			
		||||
                , Station.ByHexStringToBytes().Reverse().ToArray());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,109 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.String;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DLT645_2007Address
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class DLT645_2007Address : DeviceAddressBase
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public DLT645_2007Address()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 数据标识
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public byte[] DataId { get; set; } = new byte[0];
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 反转解析
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public bool Reverse { get; set; } = true;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 站号信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public byte[] Station { get; set; } = new byte[0];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 解析地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="address"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static DLT645_2007Address ParseFrom(string address)
 | 
			
		||||
    {
 | 
			
		||||
        DLT645_2007Address dLT645_2007Address = new();
 | 
			
		||||
        byte[] array;
 | 
			
		||||
        array = new byte[0];
 | 
			
		||||
        if (address.IndexOf(';') < 0)
 | 
			
		||||
        {
 | 
			
		||||
            array = address.ByHexStringToBytes().Reverse().ToArray();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            string[] strArray = address.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
 | 
			
		||||
            for (int index = 0; index < strArray.Length; ++index)
 | 
			
		||||
            {
 | 
			
		||||
                if (strArray[index].ToUpper().StartsWith("S="))
 | 
			
		||||
                {
 | 
			
		||||
                    var station = strArray[index].Substring(2);
 | 
			
		||||
                    if (station.IsNullOrEmpty()) station = string.Empty;
 | 
			
		||||
                    if (station.Length < 12)
 | 
			
		||||
                        station = station.PadLeft(12, '0');
 | 
			
		||||
                    dLT645_2007Address.Station = station.ByHexStringToBytes().Reverse().ToArray();
 | 
			
		||||
                }
 | 
			
		||||
                else if (strArray[index].Contains("r="))
 | 
			
		||||
                {
 | 
			
		||||
                    dLT645_2007Address.Reverse = strArray[index].Substring(2).GetBoolValue();
 | 
			
		||||
                }
 | 
			
		||||
                else if (!strArray[index].Contains("="))
 | 
			
		||||
                {
 | 
			
		||||
                    array = strArray[index].ByHexStringToBytes().Reverse().ToArray();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        dLT645_2007Address.DataId = array;
 | 
			
		||||
        return dLT645_2007Address;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder stringGeter = new();
 | 
			
		||||
        if (Station.Length > 0)
 | 
			
		||||
        {
 | 
			
		||||
            stringGeter.Append("s=" + Station.Reverse().ToArray().ToHexString() + ";");
 | 
			
		||||
        }
 | 
			
		||||
        if (DataId.Length > 0)
 | 
			
		||||
        {
 | 
			
		||||
            stringGeter.Append(DataId.Reverse().ToArray().ToHexString() + ";");
 | 
			
		||||
        }
 | 
			
		||||
        if (!Reverse)
 | 
			
		||||
        {
 | 
			
		||||
            stringGeter.Append("s=" + Reverse.ToString() + ";");
 | 
			
		||||
        }
 | 
			
		||||
        return stringGeter.ToString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,117 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DLT645_2007
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class DLT645_2007BitConverter : ThingsGatewayBitConverter
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// DLT645_2007
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public DLT645_2007BitConverter(EndianType endianType) : base(endianType)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// DLT645协议转换double
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="buffer">带数据项标识</param>
 | 
			
		||||
    /// <param name="offset"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public override double ToDouble(byte[] buffer, int offset)
 | 
			
		||||
    {
 | 
			
		||||
        return Convert.ToDouble(this.ToString(buffer, offset, buffer.Length));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override IThingsGatewayBitConverter CopyNew()
 | 
			
		||||
    {
 | 
			
		||||
        return new DLT645_2007BitConverter(EndianType)
 | 
			
		||||
        {
 | 
			
		||||
            DataFormat = DataFormat,
 | 
			
		||||
            BcdFormat = BcdFormat,
 | 
			
		||||
            Encoding = Encoding,
 | 
			
		||||
            IsStringReverseByteWord = IsStringReverseByteWord,
 | 
			
		||||
            Length = Length,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string ToString(byte[] buffer)
 | 
			
		||||
    {
 | 
			
		||||
        return this.ToString(buffer, 0, buffer.Length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string ToString(byte[] buffer, int offset, int length)
 | 
			
		||||
    {
 | 
			
		||||
        buffer = buffer.RemoveBegin(offset);
 | 
			
		||||
        buffer = buffer.BytesAdd(-0x33);
 | 
			
		||||
        var dataInfos = DLT645Helper.GetDataInfos(buffer);
 | 
			
		||||
        StringBuilder stringBuilder = new();
 | 
			
		||||
        foreach (var dataInfo in dataInfos)
 | 
			
		||||
        {
 | 
			
		||||
            //实际数据
 | 
			
		||||
            var content = buffer.SelectMiddle(4, dataInfo.ByteLength).Reverse().ToArray();
 | 
			
		||||
            if (dataInfo.IsSigned)//可能为负数
 | 
			
		||||
            {
 | 
			
		||||
                if (content[0] > 0x80)//最高位是表示正负
 | 
			
		||||
                {
 | 
			
		||||
                    content[0] = (byte)(content[0] - 0x80);
 | 
			
		||||
                    if (dataInfo.Digtal == 0)//无小数点
 | 
			
		||||
                    {
 | 
			
		||||
                        stringBuilder.Append($"-{content.ToHexString()}");
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        stringBuilder.Append((-(Convert.ToDouble(content.ToHexString()) / Math.Pow(10.0, dataInfo.Digtal))).ToString());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    ToString(stringBuilder, dataInfo, content);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                ToString(stringBuilder, dataInfo, content);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return stringBuilder.ToString();
 | 
			
		||||
 | 
			
		||||
        static void ToString(StringBuilder stringBuilder, DataInfo dataInfo, byte[] content)
 | 
			
		||||
        {
 | 
			
		||||
            if (dataInfo.Digtal < 0)
 | 
			
		||||
            {
 | 
			
		||||
                stringBuilder.Append($"{Encoding.ASCII.GetString(content)}");
 | 
			
		||||
            }
 | 
			
		||||
            else if (dataInfo.Digtal == 0)//无小数点
 | 
			
		||||
            {
 | 
			
		||||
                stringBuilder.Append($"{content.ToHexString()}");
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                stringBuilder.Append(((Convert.ToDouble(content.ToHexString()) / Math.Pow(10.0, dataInfo.Digtal))).ToString());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,191 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DLT645_2007DataHandleAdapter
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class DLT645_2007DataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<DLT645_2007Message>
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 增加FE FE FE FE的报文头部
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("前导符报文头")]
 | 
			
		||||
    public bool EnableFEHead { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override byte[] PackCommand(byte[] command)
 | 
			
		||||
    {
 | 
			
		||||
        //打包时加上4个FE字节
 | 
			
		||||
        if (EnableFEHead)
 | 
			
		||||
        {
 | 
			
		||||
            return DataTransUtil.SpliceArray(new byte[4] { 0xFE, 0xFE, 0xFE, 0xFE }, command);
 | 
			
		||||
        }
 | 
			
		||||
        return command;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override DLT645_2007Message GetInstance()
 | 
			
		||||
    {
 | 
			
		||||
        return new DLT645_2007Message();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override FilterResult UnpackResponse(DLT645_2007Message request, byte[] send, byte[] body, byte[] response)
 | 
			
		||||
    {
 | 
			
		||||
        //因为设备可能带有FE前导符开头,这里找到0x68的位置
 | 
			
		||||
        int headCodeIndex = -1;
 | 
			
		||||
        if (response != null)
 | 
			
		||||
        {
 | 
			
		||||
            for (int index = 0; index < response.Length; index++)
 | 
			
		||||
            {
 | 
			
		||||
                if (response[index] == 0x68)
 | 
			
		||||
                {
 | 
			
		||||
                    headCodeIndex = index;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        int sendHeadCodeIndex = 0;
 | 
			
		||||
        if (send != null)
 | 
			
		||||
        {
 | 
			
		||||
            for (int index = 0; index < send.Length; index++)
 | 
			
		||||
            {
 | 
			
		||||
                if (send[index] == 0x68)
 | 
			
		||||
                {
 | 
			
		||||
                    sendHeadCodeIndex = index;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //帧起始符 地址域  帧起始符 控制码 数据域长度共10个字节
 | 
			
		||||
        if (headCodeIndex < 0 || headCodeIndex + 10 > response.Length)
 | 
			
		||||
            return FilterResult.Cache;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var len = 10 + response[headCodeIndex + 9] + 2;
 | 
			
		||||
 | 
			
		||||
        if (response.Length - headCodeIndex < len)
 | 
			
		||||
        {
 | 
			
		||||
            return FilterResult.Cache;
 | 
			
		||||
        }
 | 
			
		||||
        if (response.Length - headCodeIndex >= len && response[len + headCodeIndex - 1] == 0x16)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            //检查校验码
 | 
			
		||||
            int sumCheck = 0;
 | 
			
		||||
            for (int i = headCodeIndex; i < len + headCodeIndex - 2; i++)
 | 
			
		||||
                sumCheck += response[i];
 | 
			
		||||
            if ((byte)sumCheck != response[len + headCodeIndex - 2])
 | 
			
		||||
            {
 | 
			
		||||
                //校验错误
 | 
			
		||||
                request.Message = "和校验错误";
 | 
			
		||||
                request.ErrorCode = 999;
 | 
			
		||||
                return FilterResult.Success;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                (response[headCodeIndex + 1] != send[sendHeadCodeIndex + 1]) ||
 | 
			
		||||
                (response[headCodeIndex + 2] != send[sendHeadCodeIndex + 2]) ||
 | 
			
		||||
                (response[headCodeIndex + 3] != send[sendHeadCodeIndex + 3]) ||
 | 
			
		||||
                (response[headCodeIndex + 4] != send[sendHeadCodeIndex + 4]) ||
 | 
			
		||||
                (response[headCodeIndex + 5] != send[sendHeadCodeIndex + 5]) ||
 | 
			
		||||
                (response[headCodeIndex + 6] != send[sendHeadCodeIndex + 6])
 | 
			
		||||
                )//设备地址不符合时,返回错误
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                if (
 | 
			
		||||
                (send[sendHeadCodeIndex + 1] == 0xAA) &&
 | 
			
		||||
                (send[sendHeadCodeIndex + 2] == 0xAA) &&
 | 
			
		||||
                (send[sendHeadCodeIndex + 3] == 0xAA) &&
 | 
			
		||||
                (send[sendHeadCodeIndex + 4] == 0xAA) &&
 | 
			
		||||
                (send[sendHeadCodeIndex + 5] == 0xAA) &&
 | 
			
		||||
                (send[sendHeadCodeIndex + 6] == 0xAA)
 | 
			
		||||
                )//读写通讯地址例外
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    request.Message = "返回地址不符合规则";
 | 
			
		||||
                    request.ErrorCode = 999;
 | 
			
		||||
                    return FilterResult.Success;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            if ((response[headCodeIndex + 8] != send[sendHeadCodeIndex + 8] + 0x80))//控制码不符合时,返回错误
 | 
			
		||||
            {
 | 
			
		||||
                request.Message = "返回控制码:" + $"0x{response[headCodeIndex + 8]:X2},请求控制码:" + $"0x{send[sendHeadCodeIndex + 8]:X2},不符合规则";
 | 
			
		||||
                request.ErrorCode = 999;
 | 
			
		||||
                return FilterResult.Success;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            if ((response[headCodeIndex + 8] & 0x40) == 0x40)//控制码bit6为1时,返回错误
 | 
			
		||||
            {
 | 
			
		||||
                byte byte1 = (byte)(response[headCodeIndex + 10] - 0x33);
 | 
			
		||||
                var error = DLT645Helper.Get2007ErrorMessage(byte1);
 | 
			
		||||
                request.Message = "异常控制码:" + $"0x{response[headCodeIndex + 8]:X2},错误信息:{error}";
 | 
			
		||||
                request.ErrorCode = 999;
 | 
			
		||||
                return FilterResult.Success;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (send[sendHeadCodeIndex + 8] == (byte)ControlCode.Read ||
 | 
			
		||||
    send[sendHeadCodeIndex + 8] == (byte)ControlCode.Write
 | 
			
		||||
    )
 | 
			
		||||
            {
 | 
			
		||||
                //数据标识不符合时,返回错误
 | 
			
		||||
                if (
 | 
			
		||||
                (response[headCodeIndex + 10] == send[sendHeadCodeIndex + 10]) &&
 | 
			
		||||
                (response[headCodeIndex + 11] == send[sendHeadCodeIndex + 11]) &&
 | 
			
		||||
                (response[headCodeIndex + 12] == send[sendHeadCodeIndex + 12]) &&
 | 
			
		||||
                (response[headCodeIndex + 13] == send[sendHeadCodeIndex + 13])
 | 
			
		||||
                )
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    request.Message = "返回数据标识不符合规则";
 | 
			
		||||
                    request.ErrorCode = 999;
 | 
			
		||||
                    return FilterResult.Success;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            request.Content = response.RemoveBegin(headCodeIndex + 10).RemoveLast(response.Length + 2 - len - headCodeIndex);
 | 
			
		||||
            request.ErrorCode = 0;
 | 
			
		||||
            return FilterResult.Success;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            request.ErrorCode = 999;
 | 
			
		||||
            return FilterResult.Success;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class DLT645_2007Message : MessageBase, IMessage
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override int HeadBytesLength => -1;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override bool CheckHeadBytes(byte[] heads)
 | 
			
		||||
    {
 | 
			
		||||
        BodyLength = -1;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,480 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DLT645_2007
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class DLT645_2007OverTcp : ReadWriteDevicesTcpClientBase
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// DLT645_2007
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="tcpClient"></param>
 | 
			
		||||
    public DLT645_2007OverTcp(TcpClient tcpClient) : base(tcpClient)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new DLT645_2007BitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 增加FE FE FE FE的报文头部
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("前导符报文头")]
 | 
			
		||||
    public bool EnableFEHead { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 写入需操作员代码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("操作员代码")]
 | 
			
		||||
    public string OperCode { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 写入密码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("写入密码")]
 | 
			
		||||
    public string Password { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通讯地址BCD码,一般应该是12个字符
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("通讯地址")]
 | 
			
		||||
    public string Station { get; set; }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        var str = """
 | 
			
		||||
            查看附带文档或者相关资料,下面列举一下常见的数据标识地址 
 | 
			
		||||
            
 | 
			
		||||
            地址                       说明                    
 | 
			
		||||
            -----------------------------------------
 | 
			
		||||
            02010100    A相电压
 | 
			
		||||
            02020100    A相电流
 | 
			
		||||
            02030000    瞬时总有功功率
 | 
			
		||||
            00000000    (当前)组合有功总电能
 | 
			
		||||
            00010000    (当前)正向有功总电能
 | 
			
		||||
            
 | 
			
		||||
            """;
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + str;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<byte[]>(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Read, Station);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<byte[]>(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        var dataHandleAdapter = new DLT645_2007DataHandleAdapter
 | 
			
		||||
        {
 | 
			
		||||
            EnableFEHead = EnableFEHead
 | 
			
		||||
        };
 | 
			
		||||
        TcpClient.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, string value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            Password ??= string.Empty;
 | 
			
		||||
            OperCode ??= string.Empty;
 | 
			
		||||
            if (Password.Length < 8)
 | 
			
		||||
                Password = Password.PadLeft(8, '0');
 | 
			
		||||
            if (OperCode.Length < 8)
 | 
			
		||||
                OperCode = OperCode.PadLeft(8, '0');
 | 
			
		||||
            var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
 | 
			
		||||
            string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = WaitingClientEx.SendThenResponse(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default) => Write(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, string value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            Password ??= string.Empty;
 | 
			
		||||
            OperCode ??= string.Empty;
 | 
			
		||||
            if (Password.Length < 8)
 | 
			
		||||
                Password = Password.PadLeft(8, '0');
 | 
			
		||||
            if (OperCode.Length < 8)
 | 
			
		||||
                OperCode = OperCode.PadLeft(8, '0');
 | 
			
		||||
            var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
 | 
			
		||||
            string[] strArray = value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command(address, (byte)ControlCode.Write, Station, data, strArray);
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                return (MessageBase)result.RequestInfo;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, uint value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, double value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, float value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, long value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, ulong value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, ushort value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, short value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, int value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), cancellationToken);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #region 其他方法
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 广播校时
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="dateTime"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public OperResult BroadcastTime(DateTime dateTime, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            string str = $"{dateTime.Second:D2}{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}{dateTime.Year % 100:D2}";
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.BroadcastTime, str.ByHexStringToBytes().Reverse().ToArray(), "999999999999".ByHexStringToBytes());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                TcpClient.Send(commandResult.Content);
 | 
			
		||||
                return OperResult.CreateSuccessResult();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 冻结
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="dateTime"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> FreezeAsync(DateTime dateTime, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            string str = $"{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}";
 | 
			
		||||
            if (Station.IsNullOrEmpty()) Station = string.Empty;
 | 
			
		||||
            if (Station.Length < 12) Station = Station.PadLeft(12, '0');
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.Freeze, str.ByHexStringToBytes().Reverse().ToArray(), Station.ByHexStringToBytes().Reverse().ToArray());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取通信地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult<string>> ReadDeviceStationAsync(CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.ReadStation, null, "AAAAAAAAAAAA".ByHexStringToBytes());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    var buffer = result1.Content.SelectMiddle(0, 6).BytesAdd(-0x33);
 | 
			
		||||
                    return OperResult.CreateSuccessResult(buffer.Reverse().ToArray().ToHexString());
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult<string>(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<string>(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 修改波特率
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="baudRate"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> WriteBaudRateAsync(int baudRate, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            byte baudRateByte;
 | 
			
		||||
            switch (baudRate)
 | 
			
		||||
            {
 | 
			
		||||
                case 600: baudRateByte = 0x02; break;
 | 
			
		||||
                case 1200: baudRateByte = 0x04; break;
 | 
			
		||||
                case 2400: baudRateByte = 0x08; break;
 | 
			
		||||
                case 4800: baudRateByte = 0x10; break;
 | 
			
		||||
                case 9600: baudRateByte = 0x20; break;
 | 
			
		||||
                case 19200: baudRateByte = 0x40; break;
 | 
			
		||||
                default: return new OperResult<string>($"不支持此波特率:{baudRate}");
 | 
			
		||||
            }
 | 
			
		||||
            if (Station.IsNullOrEmpty()) Station = string.Empty;
 | 
			
		||||
            if (Station.Length < 12) Station = Station.PadLeft(12, '0');
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteBaudRate, new byte[] { baudRateByte }, Station.ByHexStringToBytes().Reverse().ToArray());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 更新通信地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="station"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> WriteDeviceStationAsync(string station, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WriteStation, station.ByHexStringToBytes().Reverse().ToArray(), "AAAAAAAAAAAA".ByHexStringToBytes());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 修改密码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="level"></param>
 | 
			
		||||
    /// <param name="oldPassword"></param>
 | 
			
		||||
    /// <param name="newPassword"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public async Task<OperResult> WritePasswordAsync(byte level, string oldPassword, string newPassword, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
 | 
			
		||||
            if (Station.IsNullOrEmpty()) Station = string.Empty;
 | 
			
		||||
            if (Station.Length < 12) Station = Station.PadLeft(12, '0');
 | 
			
		||||
            string str = $"04000C{level:D2}";
 | 
			
		||||
 | 
			
		||||
            var commandResult = DLT645Helper.GetDLT645_2007Command((byte)ControlCode.WritePassword,
 | 
			
		||||
                str.ByHexStringToBytes().Reverse().ToArray()
 | 
			
		||||
                .SpliceArray(oldPassword.ByHexStringToBytes().Reverse().ToArray())
 | 
			
		||||
                .SpliceArray(newPassword.ByHexStringToBytes().Reverse().ToArray())
 | 
			
		||||
                , Station.ByHexStringToBytes().Reverse().ToArray());
 | 
			
		||||
            if (commandResult.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                var result = await WaitingClientEx.SendThenResponseAsync(commandResult.Content, TimeOut, cancellationToken);
 | 
			
		||||
                var result1 = ((MessageBase)result.RequestInfo);
 | 
			
		||||
                if (result1.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult(result1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(commandResult);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<string>(ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.DLT645;
 | 
			
		||||
 | 
			
		||||
internal static class PackHelper
 | 
			
		||||
{
 | 
			
		||||
    public static List<T> LoadSourceRead<T, T2>(IReadWrite device, List<T2> deviceVariables, int maxPack) where T : IDeviceVariableSourceRead<IDeviceVariableRunTime>, new() where T2 : IDeviceVariableRunTime, new()
 | 
			
		||||
    {
 | 
			
		||||
        var byteConverter = device.ThingsGatewayBitConverter;
 | 
			
		||||
        var result = new List<T>();
 | 
			
		||||
        //需要先剔除额外信息,比如dataformat等
 | 
			
		||||
        foreach (var item in deviceVariables)
 | 
			
		||||
        {
 | 
			
		||||
            var address = item.VariableAddress;
 | 
			
		||||
 | 
			
		||||
            IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, byteConverter);
 | 
			
		||||
            item.ThingsGatewayBitConverter = transformParameter;
 | 
			
		||||
            //item.VariableAddress = address;
 | 
			
		||||
            item.Index = device.GetBitOffset(item.VariableAddress);
 | 
			
		||||
 | 
			
		||||
            result.Add(new()
 | 
			
		||||
            {
 | 
			
		||||
                DeviceVariableRunTimes = new() { item },
 | 
			
		||||
                VariableAddress = address,
 | 
			
		||||
                Length = 1,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
global using System;
 | 
			
		||||
global using System.Collections.Generic;
 | 
			
		||||
global using System.Linq;
 | 
			
		||||
global using System.Threading;
 | 
			
		||||
global using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
global using ThingsGateway.Foundation.Core;
 | 
			
		||||
global using ThingsGateway.Foundation.Serial;
 | 
			
		||||
global using ThingsGateway.Foundation.Sockets;
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
global using System;
 | 
			
		||||
global using System.Collections.Generic;
 | 
			
		||||
global using System.Linq;
 | 
			
		||||
global using System.Threading;
 | 
			
		||||
global using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
global using ThingsGateway.Foundation.Core;
 | 
			
		||||
global using ThingsGateway.Foundation.Serial;
 | 
			
		||||
global using ThingsGateway.Foundation.Sockets;
 | 
			
		||||
@@ -12,6 +12,9 @@
 | 
			
		||||
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.String;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -26,22 +29,10 @@ public class ModbusAddress : DeviceAddressBase
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusAddress(string address, ushort len)
 | 
			
		||||
    {
 | 
			
		||||
        Station = 0;
 | 
			
		||||
        AddressStart = 0;
 | 
			
		||||
        Parse(address, len);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusAddress(string address, byte station)
 | 
			
		||||
    {
 | 
			
		||||
        Station = station;
 | 
			
		||||
        AddressStart = 0;
 | 
			
		||||
        Parse(address, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取功能码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ushort AddressStart => Address.ToUShort();
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取功能码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -56,15 +47,44 @@ public class ModbusAddress : DeviceAddressBase
 | 
			
		||||
    /// 写入功能码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public byte WriteFunction { get; set; }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 打包临时写入,需要读取的字节长度
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int ByteLength { get; set; }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// BitIndex
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int BitIndex => (int)(Address.SplitDot().LastOrDefault().ToInt());
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void Parse(string address, int length)
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取功能码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ushort AddressEnd => (ushort)(AddressStart + (Math.Ceiling(ByteLength / 2.0) > 0 ? Math.Ceiling(ByteLength / 2.0) : 1));
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 作为Slave时需提供的SocketId,用于分辨Socket客户端,通常对比的是初始链接时的注册包
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string SocketId { get; set; }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 解析地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static ModbusAddress ParseFrom(string address, byte station)
 | 
			
		||||
    {
 | 
			
		||||
        Length = length;
 | 
			
		||||
        ModbusAddress modbusAddress = new()
 | 
			
		||||
        {
 | 
			
		||||
            Station = station
 | 
			
		||||
        };
 | 
			
		||||
        return ParseFrom(address, modbusAddress);
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 解析地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static ModbusAddress ParseFrom(string address, ModbusAddress modbusAddress = null)
 | 
			
		||||
    {
 | 
			
		||||
        modbusAddress ??= new();
 | 
			
		||||
        if (address.IndexOf(';') < 0)
 | 
			
		||||
        {
 | 
			
		||||
            Address(address);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
@@ -74,12 +94,16 @@ public class ModbusAddress : DeviceAddressBase
 | 
			
		||||
                if (strArray[index].ToUpper().StartsWith("S="))
 | 
			
		||||
                {
 | 
			
		||||
                    if (Convert.ToInt16(strArray[index].Substring(2)) > 0)
 | 
			
		||||
                        Station = byte.Parse(strArray[index].Substring(2));
 | 
			
		||||
                        modbusAddress.Station = byte.Parse(strArray[index].Substring(2));
 | 
			
		||||
                }
 | 
			
		||||
                else if (strArray[index].ToUpper().StartsWith("W="))
 | 
			
		||||
                {
 | 
			
		||||
                    if (Convert.ToInt16(strArray[index].Substring(2)) > 0)
 | 
			
		||||
                        this.WriteFunction = byte.Parse(strArray[index].Substring(2));
 | 
			
		||||
                        modbusAddress.WriteFunction = byte.Parse(strArray[index].Substring(2));
 | 
			
		||||
                }
 | 
			
		||||
                else if (strArray[index].ToUpper().StartsWith("ID="))
 | 
			
		||||
                {
 | 
			
		||||
                    modbusAddress.SocketId = strArray[index].Substring(3);
 | 
			
		||||
                }
 | 
			
		||||
                else if (!strArray[index].Contains("="))
 | 
			
		||||
                {
 | 
			
		||||
@@ -88,17 +112,35 @@ public class ModbusAddress : DeviceAddressBase
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return modbusAddress;
 | 
			
		||||
 | 
			
		||||
        void Address(string address)
 | 
			
		||||
        {
 | 
			
		||||
            var readF = ushort.Parse(address.Substring(0, 1));
 | 
			
		||||
            if (readF > 4)
 | 
			
		||||
                throw new("功能码错误");
 | 
			
		||||
            GetFunction(readF);
 | 
			
		||||
            AddressStart = int.Parse(address.Substring(1)) - 1;
 | 
			
		||||
            switch (readF)
 | 
			
		||||
            {
 | 
			
		||||
                case 0:
 | 
			
		||||
                    modbusAddress.ReadFunction = 1;
 | 
			
		||||
                    break;
 | 
			
		||||
                case 1:
 | 
			
		||||
                    modbusAddress.ReadFunction = 2;
 | 
			
		||||
                    break;
 | 
			
		||||
                case 3:
 | 
			
		||||
                    modbusAddress.ReadFunction = 4;
 | 
			
		||||
                    break;
 | 
			
		||||
                case 4:
 | 
			
		||||
                    modbusAddress.ReadFunction = 3;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            modbusAddress.Address = (double.Parse(address.Substring(1)) - 1).ToString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
@@ -111,28 +153,14 @@ public class ModbusAddress : DeviceAddressBase
 | 
			
		||||
        {
 | 
			
		||||
            stringGeter.Append("w=" + WriteFunction.ToString() + ";");
 | 
			
		||||
        }
 | 
			
		||||
        if (!string.IsNullOrEmpty(SocketId))
 | 
			
		||||
        {
 | 
			
		||||
            stringGeter.Append("id=" + SocketId + ";");
 | 
			
		||||
        }
 | 
			
		||||
        stringGeter.Append(GetFunctionString(ReadFunction) + (AddressStart + 1).ToString());
 | 
			
		||||
        return stringGeter.ToString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void GetFunction(ushort readF)
 | 
			
		||||
    {
 | 
			
		||||
        switch (readF)
 | 
			
		||||
        {
 | 
			
		||||
            case 0:
 | 
			
		||||
                ReadFunction = 1;
 | 
			
		||||
                break;
 | 
			
		||||
            case 1:
 | 
			
		||||
                ReadFunction = 2;
 | 
			
		||||
                break;
 | 
			
		||||
            case 3:
 | 
			
		||||
                ReadFunction = 4;
 | 
			
		||||
                break;
 | 
			
		||||
            case 4:
 | 
			
		||||
                ReadFunction = 3;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private string GetFunctionString(int readF)
 | 
			
		||||
    {
 | 
			
		||||
        return readF switch
 | 
			
		||||
@@ -0,0 +1,334 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Bool;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
internal class ModbusHelper
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 添加Crc16
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static byte[] AddCrc(byte[] command)
 | 
			
		||||
    {
 | 
			
		||||
        return EasyCRC16.CRC16(command);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 添加ModbusTcp报文头
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static byte[] AddModbusTcpHead(byte[] modbus, ushort id)
 | 
			
		||||
    {
 | 
			
		||||
        byte[] tcp = new byte[modbus.Length + 6];
 | 
			
		||||
        tcp[0] = BitConverter.GetBytes(id)[1];
 | 
			
		||||
        tcp[1] = BitConverter.GetBytes(id)[0];
 | 
			
		||||
        tcp[4] = BitConverter.GetBytes(modbus.Length)[1];
 | 
			
		||||
        tcp[5] = BitConverter.GetBytes(modbus.Length)[0];
 | 
			
		||||
        modbus.CopyTo(tcp, 6);
 | 
			
		||||
        return tcp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// modbus地址格式说明
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder stringBuilder = new();
 | 
			
		||||
        stringBuilder.AppendLine("Modbus寄存器");
 | 
			
		||||
        stringBuilder.AppendLine("线圈寄存器使用从 00001 开始的地址编号。");
 | 
			
		||||
        stringBuilder.AppendLine("离散输入寄存器使用从 10001 开始的地址编号。");
 | 
			
		||||
        stringBuilder.AppendLine("输入寄存器使用从 30001 开始的地址编号。");
 | 
			
		||||
        stringBuilder.AppendLine("保持寄存器使用从 40001 开始的地址编号。");
 | 
			
		||||
        stringBuilder.AppendLine("举例:");
 | 
			
		||||
        stringBuilder.AppendLine("40001=>保持寄存器第一个寄存器");
 | 
			
		||||
        stringBuilder.AppendLine("额外格式:");
 | 
			
		||||
        stringBuilder.AppendLine("设备站号 ,比如40001;s=2; ,代表设备地址为2的保持寄存器第一个寄存器");
 | 
			
		||||
        stringBuilder.AppendLine("写入功能码 ,比如40001;w=16; ,代表保持寄存器第一个寄存器,写入值时采用0x10功能码,而不是默认的0x06功能码");
 | 
			
		||||
        return stringBuilder.ToString();
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 通过错误码来获取到对应的文本消息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static string GetDescriptionByErrorCode(byte code)
 | 
			
		||||
    {
 | 
			
		||||
        return code switch
 | 
			
		||||
        {
 | 
			
		||||
            1 => "不支持的功能码",
 | 
			
		||||
            2 => "读取寄存器越界",
 | 
			
		||||
            3 => "读取长度超限",
 | 
			
		||||
            4 => "读写异常",
 | 
			
		||||
            _ => "未知错误",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取modbus数据区内容,返回数据需去除Crc和报文头,例如:01 03 02 00 01,发送数据需报文头
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="send">发送数据</param>
 | 
			
		||||
    /// <param name="response">返回数据</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static OperResult<byte[], FilterResult> GetModbusData(byte[] send, byte[] response)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (response.Length < 3)
 | 
			
		||||
                return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            if (response[1] >= 0x80)//错误码
 | 
			
		||||
                return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success };
 | 
			
		||||
            if (response[1] <= 0x05)
 | 
			
		||||
            {
 | 
			
		||||
                if ((response.Length < response[2] + 3))
 | 
			
		||||
                    return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if ((response.Length < 6))
 | 
			
		||||
                    return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (send.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var result = OperResult.CreateSuccessResult(GenericExtensions.ArrayRemoveBegin(response, 3), FilterResult.Success);
 | 
			
		||||
                result.Message = "接收数据正确,但主机并没有主动请求数据";
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
            if (send[0] != response[0])
 | 
			
		||||
                return new OperResult<byte[], FilterResult>(string.Format("站号不一致", send[0], response[0])) { Content2 = FilterResult.Success };
 | 
			
		||||
            if (send[1] != response[1])
 | 
			
		||||
                return new OperResult<byte[], FilterResult>() { Message = "功能码不一致", Content2 = FilterResult.Success };
 | 
			
		||||
            return OperResult.CreateSuccessResult(GenericExtensions.ArrayRemoveBegin(response, 3), FilterResult.Success);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[], FilterResult>(ex.Message) { Content2 = FilterResult.Success };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 去除Crc,返回modbus数据区
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="send"></param>
 | 
			
		||||
    /// <param name="response"></param>
 | 
			
		||||
    /// <param name="crcCheck"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static OperResult<byte[], FilterResult> GetModbusRtuData(byte[] send, byte[] response, bool crcCheck = true)
 | 
			
		||||
    {
 | 
			
		||||
        if (response.Length < 3)
 | 
			
		||||
            return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
 | 
			
		||||
 | 
			
		||||
        if (response[1] >= 0x80)//错误码
 | 
			
		||||
            return new OperResult<byte[], FilterResult>(GetDescriptionByErrorCode(response[2])) { Content2 = FilterResult.Success };
 | 
			
		||||
        if (response[1] <= 0x05)
 | 
			
		||||
        {
 | 
			
		||||
            if ((response.Length < response[2] + 5))
 | 
			
		||||
                return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if ((response.Length < 8))
 | 
			
		||||
                return new OperResult<byte[], FilterResult>("数据长度不足" + response.ToHexString()) { Content2 = FilterResult.Cache };
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var data = response.SelectMiddle(0, response[2] != 0 ? response[2] + 5 : 8);
 | 
			
		||||
        if (crcCheck && !EasyCRC16.CheckCRC16(data))
 | 
			
		||||
            return new OperResult<byte[], FilterResult>("Crc校验失败" + DataTransUtil.ByteToHexString(data, ' ')) { Content2 = FilterResult.Success };
 | 
			
		||||
        return GetModbusData(send, data.RemoveLast(2));
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取读取报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static OperResult<byte[]> GetReadModbusCommand(string address, int length, byte station)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, station);
 | 
			
		||||
            return OperResult.CreateSuccessResult(GetReadModbusCommand(mAddress, length));
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取写入布尔量报文,根据地址识别功能码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static OperResult<byte[]> GetWriteBoolModbusCommand(string address, bool[] values, byte station)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, station);
 | 
			
		||||
 | 
			
		||||
            //功能码或实际长度
 | 
			
		||||
            if (values?.Length > 1 || mAddress.WriteFunction == 15)
 | 
			
		||||
                return GetWriteBoolModbusCommand(mAddress, values, values.Length);
 | 
			
		||||
            else
 | 
			
		||||
                return GetWriteBoolModbusCommand(address, values[0], station);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取写入字报文,根据地址识别功能码
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static OperResult<byte[]> GetWriteModbusCommand(string address, byte[] value, byte station)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, station);
 | 
			
		||||
 | 
			
		||||
            //功能码或实际长度
 | 
			
		||||
            if (value?.Length > 2 || mAddress.WriteFunction == 16)
 | 
			
		||||
                return OperResult.CreateSuccessResult(GetWriteModbusCommand(mAddress, value));
 | 
			
		||||
            else
 | 
			
		||||
                return OperResult.CreateSuccessResult(GetWriteOneModbusCommand(mAddress, value));
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取读取报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static byte[] GetReadModbusCommand(ModbusAddress mAddress, int length)
 | 
			
		||||
    {
 | 
			
		||||
        byte[] array = new byte[6]
 | 
			
		||||
        {
 | 
			
		||||
        (byte) mAddress.Station,
 | 
			
		||||
        (byte) mAddress.ReadFunction,
 | 
			
		||||
        BitConverter.GetBytes(mAddress.AddressStart)[1],
 | 
			
		||||
        BitConverter.GetBytes(mAddress.AddressStart)[0],
 | 
			
		||||
        BitConverter.GetBytes(length)[1],
 | 
			
		||||
        BitConverter.GetBytes(length)[0]
 | 
			
		||||
        };
 | 
			
		||||
        return array;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取05写入布尔量报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static OperResult<byte[]> GetWriteBoolModbusCommand(string address, bool value, byte station)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (address.IndexOf('.') <= 0)
 | 
			
		||||
            {
 | 
			
		||||
                var mAddress = ModbusAddress.ParseFrom(address, station);
 | 
			
		||||
 | 
			
		||||
                return OperResult.CreateSuccessResult(GetWriteBoolModbusCommand(mAddress, value));
 | 
			
		||||
            }
 | 
			
		||||
            return new("不支持写入字寄存器的某一位");
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取05写入布尔量报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private static byte[] GetWriteBoolModbusCommand(ModbusAddress mAddress, bool value)
 | 
			
		||||
    {
 | 
			
		||||
        byte[] array = new byte[6]
 | 
			
		||||
        {
 | 
			
		||||
    (byte) mAddress.Station,
 | 
			
		||||
    (byte)5,
 | 
			
		||||
    BitConverter.GetBytes(mAddress.AddressStart)[1],
 | 
			
		||||
    BitConverter.GetBytes(mAddress.AddressStart)[0],
 | 
			
		||||
     0,
 | 
			
		||||
     0
 | 
			
		||||
        };
 | 
			
		||||
        if (value)
 | 
			
		||||
        {
 | 
			
		||||
            array[4] = 0xFF;
 | 
			
		||||
            array[5] = 0;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            array[4] = 0;
 | 
			
		||||
            array[5] = 0;
 | 
			
		||||
        }
 | 
			
		||||
        return array;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取15写入布尔量报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static OperResult<byte[]> GetWriteBoolModbusCommand(ModbusAddress mAddress, bool[] values, int length)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            byte[] numArray1 = values.BoolArrayToByte();
 | 
			
		||||
            byte[] numArray2 = new byte[7 + numArray1.Length];
 | 
			
		||||
            numArray2[0] = (byte)mAddress.Station;
 | 
			
		||||
            numArray2[1] = (byte)15;
 | 
			
		||||
            numArray2[2] = BitConverter.GetBytes(mAddress.AddressStart)[1];
 | 
			
		||||
            numArray2[3] = BitConverter.GetBytes(mAddress.AddressStart)[0];
 | 
			
		||||
            numArray2[4] = (byte)(length / 256);
 | 
			
		||||
            numArray2[5] = (byte)(length % 256);
 | 
			
		||||
            numArray2[6] = (byte)numArray1.Length;
 | 
			
		||||
            numArray1.CopyTo(numArray2, 7);
 | 
			
		||||
            return OperResult.CreateSuccessResult(numArray2);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取16写入字报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static byte[] GetWriteModbusCommand(ModbusAddress mAddress, byte[] values)
 | 
			
		||||
    {
 | 
			
		||||
        byte[] numArray = new byte[7 + values.Length];
 | 
			
		||||
        numArray[0] = (byte)mAddress.Station;
 | 
			
		||||
        numArray[1] = (byte)16;
 | 
			
		||||
        numArray[2] = BitConverter.GetBytes(mAddress.AddressStart)[1];
 | 
			
		||||
        numArray[3] = BitConverter.GetBytes(mAddress.AddressStart)[0];
 | 
			
		||||
        numArray[4] = (byte)(values.Length / 2 / 256);
 | 
			
		||||
        numArray[5] = (byte)(values.Length / 2 % 256);
 | 
			
		||||
        numArray[6] = (byte)values.Length;
 | 
			
		||||
        values.CopyTo(numArray, 7);
 | 
			
		||||
        return numArray;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取6写入字报文
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal static byte[] GetWriteOneModbusCommand(ModbusAddress mAddress, byte[] values)
 | 
			
		||||
    {
 | 
			
		||||
        byte[] numArray = new byte[4 + values.Length];
 | 
			
		||||
        numArray[0] = (byte)mAddress.Station;
 | 
			
		||||
        numArray[1] = (byte)6;
 | 
			
		||||
        numArray[2] = BitConverter.GetBytes(mAddress.AddressStart)[1];
 | 
			
		||||
        numArray[3] = BitConverter.GetBytes(mAddress.AddressStart)[0];
 | 
			
		||||
        values.CopyTo(numArray, 4);
 | 
			
		||||
        return numArray;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,191 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// ModbusRtu
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class ModbusRtu : ReadWriteDevicesSerialSessionBase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// ModbusRtu
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="serialSession"></param>
 | 
			
		||||
    public ModbusRtu(SerialSession serialSession) : base(serialSession)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Crc校验
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("Crc校验")]
 | 
			
		||||
    public bool Crc16CheckEnable { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("站号")]
 | 
			
		||||
    public byte Station { get; set; } = 1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusRtuDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
        {
 | 
			
		||||
            Crc16CheckEnable = Crc16CheckEnable,
 | 
			
		||||
            CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
 | 
			
		||||
        };
 | 
			
		||||
        SerialSession.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            if (FrameTime != 0)
 | 
			
		||||
                Thread.Sleep(FrameTime);
 | 
			
		||||
            var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            await Task.Delay(FrameTime, cancellationToken);
 | 
			
		||||
            var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
@@ -42,53 +43,43 @@ public class ModbusRtuDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<M
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override FilterResult UnpackResponse(ModbusRtuMessage request, byte[] send, byte[] body, byte[] response)
 | 
			
		||||
    {
 | 
			
		||||
        //理想状态检测
 | 
			
		||||
        var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
 | 
			
		||||
        if (result.IsSuccess)
 | 
			
		||||
        //链路干扰时需剔除前缀中的多于字节,初步按站号+功能码找寻初始字节
 | 
			
		||||
        if (send?.Length > 0)
 | 
			
		||||
        {
 | 
			
		||||
            request.ResultCode = result.ResultCode;
 | 
			
		||||
            request.Message = result.Message;
 | 
			
		||||
            request.Content = result.Content;
 | 
			
		||||
            return FilterResult.Success;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (response.Length <= 1)
 | 
			
		||||
            int index = -1;
 | 
			
		||||
            for (int i = 0; i < response.Length - 1; i++)
 | 
			
		||||
            {
 | 
			
		||||
                request.ResultCode = result.ResultCode;
 | 
			
		||||
                request.Message = result.Message;
 | 
			
		||||
                request.Content = result.Content;
 | 
			
		||||
                //如果长度不足,返回缓存
 | 
			
		||||
                return FilterResult.Cache;
 | 
			
		||||
                if (response[i] == send[0] && (response[i + 1] == send[1] || response[i + 1] == (send[1] + 0x80)))
 | 
			
		||||
                {
 | 
			
		||||
                    index = i;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!(response[1] <= 0x10))
 | 
			
		||||
            if (index >= 0)
 | 
			
		||||
            {
 | 
			
		||||
                request.ResultCode = result.ResultCode;
 | 
			
		||||
                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;
 | 
			
		||||
                //功能码不对,返回放弃
 | 
			
		||||
                return FilterResult.Success;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if ((response.Length > response[2] + 4))
 | 
			
		||||
                {
 | 
			
		||||
                    request.ResultCode = result.ResultCode;
 | 
			
		||||
                    request.Message = result.Message;
 | 
			
		||||
                    request.Content = result.Content;
 | 
			
		||||
                    //如果长度已经超了,说明这段报文已经不能继续解析了,直接返回放弃
 | 
			
		||||
                    return FilterResult.Success;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    request.ResultCode = result.ResultCode;
 | 
			
		||||
                    request.Message = result.Message;
 | 
			
		||||
                    request.Content = result.Content;
 | 
			
		||||
                    //否则返回缓存
 | 
			
		||||
                    return FilterResult.Cache;
 | 
			
		||||
                }
 | 
			
		||||
                request.ErrorCode = result.ErrorCode;
 | 
			
		||||
                request.Message = result.Message;
 | 
			
		||||
            }
 | 
			
		||||
            return result.Content2;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return FilterResult.Success;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -20,15 +20,12 @@ public class ModbusRtuMessage : MessageBase, IMessage
 | 
			
		||||
    public override int HeadBytesLength => -1;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override bool CheckHeadBytes(byte[] head)
 | 
			
		||||
    public override bool CheckHeadBytes(byte[] heads)
 | 
			
		||||
    {
 | 
			
		||||
        BodyLength = -1;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void SendBytesThen()
 | 
			
		||||
    {
 | 
			
		||||
        BodyLength = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,185 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public class ModbusRtuOverTcp : ReadWriteDevicesTcpClientBase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusRtuOverTcp(TcpClient tcpClient) : base(tcpClient)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Crc校验
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("Crc校验")]
 | 
			
		||||
    public bool Crc16CheckEnable { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("站号")]
 | 
			
		||||
    public byte Station { get; set; } = 1;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusRtuDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
        {
 | 
			
		||||
            Crc16CheckEnable = Crc16CheckEnable,
 | 
			
		||||
            CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
 | 
			
		||||
        };
 | 
			
		||||
        TcpClient.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            if (FrameTime != 0)
 | 
			
		||||
                Thread.Sleep(FrameTime);
 | 
			
		||||
            var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            await Task.Delay(FrameTime, cancellationToken);
 | 
			
		||||
            var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,184 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public class ModbusRtuOverUdp : ReadWriteDevicesUdpSessionBase
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusRtuOverUdp(UdpSession udpSession) : base(udpSession)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Crc校验
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("Crc校验")]
 | 
			
		||||
    public bool Crc16CheckEnable { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("站号")]
 | 
			
		||||
    public byte Station { get; set; } = 1;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusRtuOverUdpDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
        {
 | 
			
		||||
            Crc16CheckEnable = Crc16CheckEnable,
 | 
			
		||||
        };
 | 
			
		||||
        UdpSession.Config.SetUdpDataHandlingAdapter(() =>
 | 
			
		||||
        {
 | 
			
		||||
            return dataHandleAdapter;
 | 
			
		||||
        });
 | 
			
		||||
        UdpSession.Setup(UdpSession.Config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            if (FrameTime != 0)
 | 
			
		||||
                Thread.Sleep(FrameTime);
 | 
			
		||||
            var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            await Task.Delay(FrameTime, cancellationToken);
 | 
			
		||||
            var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -35,9 +35,9 @@ public class ModbusRtuOverUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAd
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override OperResult<byte[]> UnpackResponse(
 | 
			
		||||
              byte[] send, byte[] response)
 | 
			
		||||
    protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response)
 | 
			
		||||
    {
 | 
			
		||||
        return ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
 | 
			
		||||
        var result = ModbusHelper.GetModbusRtuData(send, response, Crc16CheckEnable);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,13 +10,12 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
/// ModbusTcpDataHandleAdapter
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class ModbusTcpDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusTcpMessage>
 | 
			
		||||
{
 | 
			
		||||
@@ -56,50 +55,16 @@ public class ModbusTcpDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<M
 | 
			
		||||
        var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6));
 | 
			
		||||
        if (result.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            request.ResultCode = result.ResultCode;
 | 
			
		||||
            request.ErrorCode = result.ErrorCode;
 | 
			
		||||
            request.Message = result.Message;
 | 
			
		||||
            request.Content = result.Content;
 | 
			
		||||
            return FilterResult.Success;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            //如果返回错误,具体分析
 | 
			
		||||
            var op = result.Copy<byte[], FilterResult>();
 | 
			
		||||
            if (response.Length == 9)
 | 
			
		||||
            {
 | 
			
		||||
                if (response[7] >= 0x80)//错误码
 | 
			
		||||
                {
 | 
			
		||||
                    request.ResultCode = result.ResultCode;
 | 
			
		||||
                    request.Message = result.Message;
 | 
			
		||||
                    request.Content = result.Content;
 | 
			
		||||
                    return FilterResult.Success;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (response.Length < 10)
 | 
			
		||||
            {
 | 
			
		||||
                request.ResultCode = result.ResultCode;
 | 
			
		||||
                request.Message = result.Message;
 | 
			
		||||
                request.Content = result.Content;
 | 
			
		||||
                return FilterResult.Cache;
 | 
			
		||||
                //如果长度不足,返回缓存
 | 
			
		||||
            }
 | 
			
		||||
            if ((response.Length > response[8] + 9))
 | 
			
		||||
            {
 | 
			
		||||
                request.ResultCode = result.ResultCode;
 | 
			
		||||
                request.Message = result.Message;
 | 
			
		||||
                request.Content = result.Content;
 | 
			
		||||
                return FilterResult.Success;
 | 
			
		||||
                //如果长度已经超了,说明这段报文已经不能继续解析了,直接返回放弃
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                request.ResultCode = result.ResultCode;
 | 
			
		||||
                request.Message = result.Message;
 | 
			
		||||
                request.Content = result.Content;
 | 
			
		||||
                return FilterResult.Cache;
 | 
			
		||||
                //否则返回缓存
 | 
			
		||||
            }
 | 
			
		||||
            request.ErrorCode = result.ErrorCode;
 | 
			
		||||
            request.Message = result.Message;
 | 
			
		||||
        }
 | 
			
		||||
        return result.Content2;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,10 +23,10 @@ public class ModbusTcpMessage : MessageBase, IMessage
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public bool IsCheckMessageId { get; set; } = false;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override bool CheckHeadBytes(byte[] head)
 | 
			
		||||
    public override bool CheckHeadBytes(byte[] heads)
 | 
			
		||||
    {
 | 
			
		||||
        if (head == null || head.Length <= 0) return false;
 | 
			
		||||
        HeadBytes = head;
 | 
			
		||||
        if (heads == null || heads.Length <= 0) return false;
 | 
			
		||||
        HeadBytes = heads;
 | 
			
		||||
 | 
			
		||||
        int num = (HeadBytes[4] * 256) + HeadBytes[5];
 | 
			
		||||
        BodyLength = num;
 | 
			
		||||
@@ -0,0 +1,249 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public class ModbusTcpDtu : ReadWriteDevicesTcpServerBase
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusTcpDtu(TcpService tcpService) : base(tcpService)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
        ModbusTcpDtuPlugin modbusTcpSalvePlugin = new ModbusTcpDtuPlugin();
 | 
			
		||||
        tcpService.Config.ConfigurePlugins(a =>
 | 
			
		||||
         {
 | 
			
		||||
             a.Add(modbusTcpSalvePlugin);
 | 
			
		||||
         });
 | 
			
		||||
        tcpService.Setup(tcpService.Config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 检测事务标识符
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("检测事务标识符")]
 | 
			
		||||
    public bool IsCheckMessageId { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("站号")]
 | 
			
		||||
    public byte Station { get; set; } = 1;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return SendThenReturn(mAddress.SocketId, commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return await SendThenReturnAsync(mAddress.SocketId, commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (socketClient is SocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            ModbusTcpDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
            {
 | 
			
		||||
                IsCheckMessageId = IsCheckMessageId,
 | 
			
		||||
                CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
 | 
			
		||||
            };
 | 
			
		||||
            client.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in TcpService.GetClients())
 | 
			
		||||
            {
 | 
			
		||||
                ModbusTcpDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
                {
 | 
			
		||||
                    IsCheckMessageId = IsCheckMessageId,
 | 
			
		||||
                    CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
 | 
			
		||||
                };
 | 
			
		||||
                item.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(mAddress.SocketId, commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(mAddress.SocketId, commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(mAddress.SocketId, commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(mAddress.SocketId, commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult<byte[]> SendThenReturn(string id, OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            if (TcpService.TryGetSocketClient($"ID={id}", out var client))
 | 
			
		||||
            {
 | 
			
		||||
                SetDataAdapter(client);
 | 
			
		||||
 | 
			
		||||
                var item = commandResult.Content;
 | 
			
		||||
                if (FrameTime != 0)
 | 
			
		||||
                    Thread.Sleep(FrameTime);
 | 
			
		||||
                var WaitingClientEx = client.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
 | 
			
		||||
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Bool;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Byte;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class ModbusServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
public class ModbusTcpServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SocketClient, Task<OperResult>> WriteData;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 继电器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
 | 
			
		||||
    private ConcurrentDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 开关输入
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
 | 
			
		||||
    private ConcurrentDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 输入寄存器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
 | 
			
		||||
    private ConcurrentDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 保持寄存器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Func<ModbusAddress, byte[], IThingsGatewayBitConverter, SocketClient, OperResult> WriteData;
 | 
			
		||||
 | 
			
		||||
    private ConcurrentDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusServer(TcpService tcpService) : base(tcpService)
 | 
			
		||||
    public ModbusTcpServer(TcpService tcpService) : base(tcpService)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
@@ -56,46 +56,18 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 多站点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("多站点")]
 | 
			
		||||
    public bool MulStation { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 默认站点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("默认站点")]
 | 
			
		||||
    public byte Station { get; set; } = 1;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token = default)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Read(address, length));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(SocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusServerDataHandleAdapter dataHandleAdapter = new();
 | 
			
		||||
        client.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken token = default)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Write(address, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken token = default)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Write(address, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void Dispose(bool disposing)
 | 
			
		||||
    public override void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var item in ModbusServer01ByteBlocks)
 | 
			
		||||
        {
 | 
			
		||||
@@ -117,167 +89,28 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
        ModbusServer02ByteBlocks.Clear();
 | 
			
		||||
        ModbusServer03ByteBlocks.Clear();
 | 
			
		||||
        ModbusServer04ByteBlocks.Clear();
 | 
			
		||||
        Disconnect();
 | 
			
		||||
        base.Dispose(disposing);
 | 
			
		||||
        base.Dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void Received(SocketClient client, IRequestInfo requestInfo)
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        //接收外部报文
 | 
			
		||||
        if (requestInfo is ModbusServerMessage modbusServerMessage)
 | 
			
		||||
        {
 | 
			
		||||
            if (!modbusServerMessage.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                return;//无法解析直接返回
 | 
			
		||||
            }
 | 
			
		||||
            if (modbusServerMessage.CurModbusAddress == null)
 | 
			
		||||
            {
 | 
			
		||||
                WriteError(client, modbusServerMessage);//无法解析变量地址,返回错误码
 | 
			
		||||
            }
 | 
			
		||||
            if (modbusServerMessage.CurModbusAddress.WriteFunction == 0)//读取
 | 
			
		||||
            {
 | 
			
		||||
                var data = Read(modbusServerMessage.CurModbusAddress.ToString(), modbusServerMessage.CurModbusAddress.Length);
 | 
			
		||||
                if (data.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    var coreData = data.Content;
 | 
			
		||||
                    if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
 | 
			
		||||
                    {
 | 
			
		||||
                        coreData = data.Content.Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling((double)modbusServerMessage.CurModbusAddress.Length / 8.0));
 | 
			
		||||
                    }
 | 
			
		||||
                    var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8)
 | 
			
		||||
                        .SpliceArray(new byte[] { (byte)coreData.Length }, coreData);
 | 
			
		||||
                    sendData[5] = (byte)(sendData.Length - 6);
 | 
			
		||||
                    client.Send(sendData);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    WriteError(client, modbusServerMessage);//返回错误码
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else//写入
 | 
			
		||||
            {
 | 
			
		||||
                var coreData = modbusServerMessage.Content;
 | 
			
		||||
                if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
 | 
			
		||||
                {
 | 
			
		||||
                    //写入继电器
 | 
			
		||||
                    if (WriteData != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
 | 
			
		||||
                        if ((WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.CurModbusAddress.Length));
 | 
			
		||||
                            if (result.IsSuccess)
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteError(client, modbusServerMessage);
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            WriteError(client, modbusServerMessage);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        //写入内存区
 | 
			
		||||
                        var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.CurModbusAddress.Length));
 | 
			
		||||
                        if (result.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            WriteError(client, modbusServerMessage);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    //写入寄存器
 | 
			
		||||
                    if (WriteData != null)
 | 
			
		||||
                    {
 | 
			
		||||
 | 
			
		||||
                        if ((WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
 | 
			
		||||
                            if (result.IsSuccess)
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteError(client, modbusServerMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            WriteError(client, modbusServerMessage);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
 | 
			
		||||
                        if (result.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            WriteError(client, modbusServerMessage);
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //返回错误码
 | 
			
		||||
        static void WriteError(SocketClient client, ModbusServerMessage modbusServerMessage)
 | 
			
		||||
        {
 | 
			
		||||
            var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8)
 | 
			
		||||
.SpliceArray(new byte[] { (byte)1 });//01 lllegal function
 | 
			
		||||
            sendData[5] = (byte)(sendData.Length - 6);
 | 
			
		||||
            sendData[7] = (byte)(sendData[7] + 128);
 | 
			
		||||
            client.Send(sendData);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void WriteSuccess03(SocketClient client, ModbusServerMessage modbusServerMessage)
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 12);
 | 
			
		||||
        sendData[5] = (byte)(sendData.Length - 6);
 | 
			
		||||
        client.Send(sendData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void Init(ModbusAddress mAddress)
 | 
			
		||||
    {
 | 
			
		||||
        if (ModbusServer01ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
 | 
			
		||||
            ModbusServer01ByteBlocks[mAddress.Station].SetLength(1024 * 128);
 | 
			
		||||
        if (ModbusServer02ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
 | 
			
		||||
            ModbusServer02ByteBlocks[mAddress.Station].SetLength(1024 * 128);
 | 
			
		||||
        if (ModbusServer03ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
 | 
			
		||||
            ModbusServer03ByteBlocks[mAddress.Station].SetLength(1024 * 128);
 | 
			
		||||
        if (ModbusServer04ByteBlocks.TryAdd(mAddress.Station, new(1024 * 128)))
 | 
			
		||||
            ModbusServer04ByteBlocks[mAddress.Station].SetLength(1024 * 128);
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult<byte[]> Read(string address, int length)
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusAddress mAddress;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            mAddress = new ModbusAddress(address, Station);
 | 
			
		||||
            mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -328,12 +161,42 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
        }
 | 
			
		||||
        return new OperResult<byte[]>("功能码错误");
 | 
			
		||||
    }
 | 
			
		||||
    private OperResult Write(string address, byte[] value)
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Read(address, length));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient)
 | 
			
		||||
    {
 | 
			
		||||
        if (socketClient is SocketClient client)
 | 
			
		||||
        {
 | 
			
		||||
            ModbusTcpServerDataHandleAdapter dataHandleAdapter = new();
 | 
			
		||||
            dataHandleAdapter.CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout);
 | 
			
		||||
            client.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in TcpService.GetClients())
 | 
			
		||||
            {
 | 
			
		||||
                ModbusTcpDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
                {
 | 
			
		||||
                    CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
 | 
			
		||||
                };
 | 
			
		||||
                item.SetDataHandlingAdapter(dataHandleAdapter);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusAddress mAddress;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            mAddress = new ModbusAddress(address, Station);
 | 
			
		||||
            mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -366,12 +229,14 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
        }
 | 
			
		||||
        return new OperResult("功能码错误");
 | 
			
		||||
    }
 | 
			
		||||
    private OperResult Write(string address, bool[] value)
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusAddress mAddress;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            mAddress = new ModbusAddress(address, Station);
 | 
			
		||||
            mAddress = ModbusAddress.ParseFrom(address, Station);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -407,4 +272,170 @@ public class ModbusServer : ReadWriteDevicesTcpServerBase
 | 
			
		||||
        }
 | 
			
		||||
        return new OperResult("功能码错误");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Write(address, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Write(address, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override async Task Received(SocketClient client, ReceivedDataEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var requestInfo = e.RequestInfo;
 | 
			
		||||
            //接收外部报文
 | 
			
		||||
            if (requestInfo is ModbusTcpServerMessage modbusServerMessage)
 | 
			
		||||
            {
 | 
			
		||||
                if (modbusServerMessage.CurModbusAddress == null)
 | 
			
		||||
                {
 | 
			
		||||
                    return;//无法解析直接返回
 | 
			
		||||
                }
 | 
			
		||||
                if (!modbusServerMessage.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    return;//无法解析直接返回
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (modbusServerMessage.CurModbusAddress.WriteFunction == 0)//读取
 | 
			
		||||
                {
 | 
			
		||||
                    var data = Read(modbusServerMessage.CurModbusAddress.ToString(), modbusServerMessage.Length);
 | 
			
		||||
                    if (data.IsSuccess)
 | 
			
		||||
                    {
 | 
			
		||||
                        var coreData = data.Content;
 | 
			
		||||
                        if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
 | 
			
		||||
                        {
 | 
			
		||||
                            coreData = data.Content.Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling(modbusServerMessage.Length / 8.0));
 | 
			
		||||
                        }
 | 
			
		||||
                        var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8).SpliceArray(new byte[] { (byte)coreData.Length }, coreData);
 | 
			
		||||
                        sendData[5] = (byte)(sendData.Length - 6);
 | 
			
		||||
                        client.Send(sendData);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        WriteError(client, modbusServerMessage);//返回错误码
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else//写入
 | 
			
		||||
                {
 | 
			
		||||
                    var coreData = modbusServerMessage.Content;
 | 
			
		||||
                    if (modbusServerMessage.CurModbusAddress.ReadFunction == 1 || modbusServerMessage.CurModbusAddress.ReadFunction == 2)
 | 
			
		||||
                    {
 | 
			
		||||
                        //写入继电器
 | 
			
		||||
                        if (WriteData != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            // 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
 | 
			
		||||
                            if ((await WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
 | 
			
		||||
                            {
 | 
			
		||||
                                var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.Length));
 | 
			
		||||
                                if (result.IsSuccess)
 | 
			
		||||
                                {
 | 
			
		||||
                                    WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    WriteError(client, modbusServerMessage);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteError(client, modbusServerMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            //写入内存区
 | 
			
		||||
                            var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData.ByteToBoolArray(modbusServerMessage.Length));
 | 
			
		||||
                            if (result.IsSuccess)
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteError(client, modbusServerMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        //写入寄存器
 | 
			
		||||
                        if (WriteData != null)
 | 
			
		||||
                        {
 | 
			
		||||
 | 
			
		||||
                            if ((await WriteData(modbusServerMessage.CurModbusAddress, modbusServerMessage.Content, ThingsGatewayBitConverter, client)).IsSuccess)
 | 
			
		||||
                            {
 | 
			
		||||
                                var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
 | 
			
		||||
                                if (result.IsSuccess)
 | 
			
		||||
                                {
 | 
			
		||||
                                    WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    WriteError(client, modbusServerMessage);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteError(client, modbusServerMessage);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            var result = Write(modbusServerMessage.CurModbusAddress.ToString(), coreData);
 | 
			
		||||
                            if (result.IsSuccess)
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteSuccess03(client, modbusServerMessage);
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                WriteError(client, modbusServerMessage);
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Logger.LogError(ex, ToString());
 | 
			
		||||
        }
 | 
			
		||||
        //返回错误码
 | 
			
		||||
        static void WriteError(SocketClient client, ModbusTcpServerMessage modbusServerMessage)
 | 
			
		||||
        {
 | 
			
		||||
            var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 8)
 | 
			
		||||
.SpliceArray(new byte[] { (byte)1 });//01 lllegal function
 | 
			
		||||
            sendData[5] = (byte)(sendData.Length - 6);
 | 
			
		||||
            sendData[7] = (byte)(sendData[7] + 128);
 | 
			
		||||
            client.Send(sendData);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void WriteSuccess03(SocketClient client, ModbusTcpServerMessage modbusServerMessage)
 | 
			
		||||
    {
 | 
			
		||||
        var sendData = modbusServerMessage.ReceivedBytes.SelectMiddle(0, 12);
 | 
			
		||||
        sendData[5] = (byte)(sendData.Length - 6);
 | 
			
		||||
        client.Send(sendData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void Init(ModbusAddress mAddress)
 | 
			
		||||
    {
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusServerMessage>
 | 
			
		||||
public class ModbusTcpServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<ModbusTcpServerMessage>
 | 
			
		||||
{
 | 
			
		||||
    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)
 | 
			
		||||
        {
 | 
			
		||||
@@ -62,13 +62,13 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override ModbusServerMessage GetInstance()
 | 
			
		||||
    protected override ModbusTcpServerMessage GetInstance()
 | 
			
		||||
    {
 | 
			
		||||
        return new ModbusServerMessage();
 | 
			
		||||
        return new ModbusTcpServerMessage();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <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));
 | 
			
		||||
        if (result.IsSuccess)
 | 
			
		||||
@@ -88,11 +88,11 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
 | 
			
		||||
                    request.CurModbusAddress = new ModbusAddress()
 | 
			
		||||
                    {
 | 
			
		||||
                        Station = station,
 | 
			
		||||
                        AddressStart = addressStart,
 | 
			
		||||
                        Address = addressStart.ToString(),
 | 
			
		||||
                        WriteFunction = function,
 | 
			
		||||
                        ReadFunction = (byte)(function == 16 ? 3 : function == 15 ? 1 : 3),
 | 
			
		||||
                        Length = ThingsGatewayBitConverter.ToByte(response, 11),
 | 
			
		||||
                    };
 | 
			
		||||
                    request.Length = ThingsGatewayBitConverter.ToByte(response, 11);
 | 
			
		||||
                    request.Content = result.Content.RemoveBegin(7);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
@@ -100,11 +100,11 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
 | 
			
		||||
                    request.CurModbusAddress = new ModbusAddress()
 | 
			
		||||
                    {
 | 
			
		||||
                        Station = station,
 | 
			
		||||
                        AddressStart = addressStart,
 | 
			
		||||
                        Address = addressStart.ToString(),
 | 
			
		||||
                        WriteFunction = function,
 | 
			
		||||
                        ReadFunction = (byte)(function == 6 ? 3 : function == 5 ? 1 : 3),
 | 
			
		||||
                        Length = 1,
 | 
			
		||||
                    };
 | 
			
		||||
                    request.Length = 1;
 | 
			
		||||
                    request.Content = result.Content.RemoveBegin(4);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -113,18 +113,18 @@ public class ModbusServerDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapte
 | 
			
		||||
                request.CurModbusAddress = new ModbusAddress()
 | 
			
		||||
                {
 | 
			
		||||
                    Station = station,
 | 
			
		||||
                    AddressStart = addressStart,
 | 
			
		||||
                    Address = addressStart.ToString(),
 | 
			
		||||
                    ReadFunction = function,
 | 
			
		||||
                    Length = ThingsGatewayBitConverter.ToByte(response, 11),
 | 
			
		||||
                };
 | 
			
		||||
                request.Length = ThingsGatewayBitConverter.ToByte(response, 11);
 | 
			
		||||
            }
 | 
			
		||||
            request.ResultCode = result.ResultCode;
 | 
			
		||||
            request.ErrorCode = result.ErrorCode;
 | 
			
		||||
            request.Message = result.Message;
 | 
			
		||||
            return FilterResult.Success;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            request.ResultCode = result.ResultCode;
 | 
			
		||||
            request.ErrorCode = result.ErrorCode;
 | 
			
		||||
            request.Message = result.Message;
 | 
			
		||||
            return FilterResult.Cache;
 | 
			
		||||
        }
 | 
			
		||||
@@ -15,20 +15,24 @@ namespace ThingsGateway.Foundation.Adapter.Modbus
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ModbusServerMessage : MessageBase, IMessage
 | 
			
		||||
    public class ModbusTcpServerMessage : MessageBase, IMessage
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 当前关联的地址
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ModbusAddress CurModbusAddress { get; set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 当前读写的数据长度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Length { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public override int HeadBytesLength => 6;
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public override bool CheckHeadBytes(byte[] head)
 | 
			
		||||
        public override bool CheckHeadBytes(byte[] heads)
 | 
			
		||||
        {
 | 
			
		||||
            if (head == null || head.Length != 6) return false;
 | 
			
		||||
            HeadBytes = head;
 | 
			
		||||
            if (heads == null || heads.Length != 6) return false;
 | 
			
		||||
            HeadBytes = heads;
 | 
			
		||||
 | 
			
		||||
            int num = (HeadBytes[4] * 256) + HeadBytes[5];
 | 
			
		||||
            BodyLength = num;
 | 
			
		||||
@@ -0,0 +1,187 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
/// <inheritdoc/>
 | 
			
		||||
public class ModbusUdp : ReadWriteDevicesUdpSessionBase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ModbusUdp(UdpSession udpSession) : base(udpSession)
 | 
			
		||||
    {
 | 
			
		||||
        ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
 | 
			
		||||
        RegisterByteLength = 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 检测事务标识符
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("检测事务标识符")]
 | 
			
		||||
    public bool IsCheckMessageId { get; set; }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 站号
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("站号")]
 | 
			
		||||
    public byte Station { get; set; } = 1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override List<T> LoadSourceRead<T, T2>(List<T2> deviceVariables, int maxPack)
 | 
			
		||||
    {
 | 
			
		||||
        return PackHelper.LoadSourceRead<T, T2>(this, deviceVariables, maxPack);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string GetAddressDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return base.GetAddressDescription() + Environment.NewLine + ModbusHelper.GetAddressDescription();
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult<byte[]> Read(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetReadModbusCommand(address, length, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override void SetDataAdapter(object socketClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        ModbusUdpDataHandleAdapter dataHandleAdapter = new()
 | 
			
		||||
        {
 | 
			
		||||
            IsCheckMessageId = IsCheckMessageId
 | 
			
		||||
        };
 | 
			
		||||
        UdpSession.Config.SetUdpDataHandlingAdapter(() =>
 | 
			
		||||
        {
 | 
			
		||||
            return dataHandleAdapter;
 | 
			
		||||
        });
 | 
			
		||||
        UdpSession.Setup(UdpSession.Config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override OperResult Write(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            Connect(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return SendThenReturn(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await ConnectAsync(cancellationToken);
 | 
			
		||||
            var commandResult = ModbusHelper.GetWriteBoolModbusCommand(address, value, Station);
 | 
			
		||||
            return await SendThenReturnAsync(commandResult, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult<byte[]> SendThenReturn(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            if (FrameTime != 0)
 | 
			
		||||
                Thread.Sleep(FrameTime);
 | 
			
		||||
            var result = WaitingClientEx.SendThenResponse(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task<OperResult<byte[]>> SendThenReturnAsync(OperResult<byte[]> commandResult, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (commandResult.IsSuccess)
 | 
			
		||||
        {
 | 
			
		||||
            var item = commandResult.Content;
 | 
			
		||||
            await Task.Delay(FrameTime, cancellationToken);
 | 
			
		||||
            var result = await WaitingClientEx.SendThenResponseAsync(item, TimeOut, cancellationToken);
 | 
			
		||||
            return (MessageBase)result.RequestInfo;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<byte[]>(commandResult.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -50,6 +50,7 @@ public class ModbusUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<M
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override OperResult<byte[]> UnpackResponse(byte[] send, byte[] response)
 | 
			
		||||
    {
 | 
			
		||||
        return ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6));
 | 
			
		||||
        var result = ModbusHelper.GetModbusData(send.RemoveBegin(6), response.RemoveBegin(6));
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,131 +10,138 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Application;
 | 
			
		||||
using ThingsGateway.Application.Extensions;
 | 
			
		||||
using ThingsGateway.Foundation;
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.String;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Modbus;
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.Modbus;
 | 
			
		||||
 | 
			
		||||
internal static class ModbusHelper
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// PackHelper
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class PackHelper
 | 
			
		||||
{
 | 
			
		||||
    internal static List<DeviceVariableSourceRead> LoadSourceRead(this List<DeviceVariableRunTime> deviceVariables, IReadWriteDevice device, int MaxPack)
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 打包变量,添加到<see href="deviceVariableSourceReads"></see>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="device"></param>
 | 
			
		||||
    /// <param name="deviceVariables"></param>
 | 
			
		||||
    /// <param name="maxPack">最大打包长度</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static List<T> LoadSourceRead<T, T2>(IReadWrite device, List<T2> deviceVariables, int maxPack) where T : IDeviceVariableSourceRead<IDeviceVariableRunTime>, new() where T2 : IDeviceVariableRunTime, new()
 | 
			
		||||
    {
 | 
			
		||||
        if (deviceVariables == null)
 | 
			
		||||
            throw new ArgumentNullException(nameof(deviceVariables));
 | 
			
		||||
 | 
			
		||||
        var deviceVariableSourceReads = new List<T>();
 | 
			
		||||
        var byteConverter = device.ThingsGatewayBitConverter;
 | 
			
		||||
        var result = new List<DeviceVariableSourceRead>();
 | 
			
		||||
        //需要先剔除额外信息,比如dataformat等
 | 
			
		||||
        foreach (var item in deviceVariables)
 | 
			
		||||
        {
 | 
			
		||||
            var address = item.VariableAddress;
 | 
			
		||||
 | 
			
		||||
            IThingsGatewayBitConverter transformParameter = ByteTransformUtil.GetTransByAddress(ref address, byteConverter);
 | 
			
		||||
            item.ThingsGatewayBitConverter = transformParameter;
 | 
			
		||||
            //item.VariableAddress = address;
 | 
			
		||||
            item.Index = device.GetBitOffset(item.VariableAddress);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //按读取间隔分组
 | 
			
		||||
        var tags = deviceVariables.GroupBy(it => it.IntervalTime);
 | 
			
		||||
        foreach (var item in tags)
 | 
			
		||||
        var deviceVariableRunTimeGroups = deviceVariables.GroupBy(it => it.IntervalTime);
 | 
			
		||||
        foreach (var group in deviceVariableRunTimeGroups)
 | 
			
		||||
        {
 | 
			
		||||
            Dictionary<ModbusAddress, DeviceVariableRunTime> map = item.ToDictionary(it =>
 | 
			
		||||
            Dictionary<ModbusAddress, T2> map = group.ToDictionary(it =>
 | 
			
		||||
            {
 | 
			
		||||
                var lastLen = it.DataTypeEnum.GetByteLength();
 | 
			
		||||
                if (lastLen <= 0)
 | 
			
		||||
                {
 | 
			
		||||
                    if (it.DataTypeEnum.GetSystemType() == typeof(bool))
 | 
			
		||||
                    switch (it.DataTypeEnum)
 | 
			
		||||
                    {
 | 
			
		||||
                        lastLen = 2;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (it.DataTypeEnum.GetSystemType() == typeof(string))
 | 
			
		||||
                    {
 | 
			
		||||
                        lastLen = it.ThingsGatewayBitConverter.StringLength;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (it.DataTypeEnum.GetSystemType() == typeof(object))
 | 
			
		||||
                    {
 | 
			
		||||
                        lastLen = 2;
 | 
			
		||||
                        case DataTypeEnum.String:
 | 
			
		||||
                            lastLen = it.ThingsGatewayBitConverter.Length == null ? throw new("数据类型为字符串时,必须指定字符串长度,才能进行打包") : it.ThingsGatewayBitConverter.Length.Value;
 | 
			
		||||
                            break;
 | 
			
		||||
                        default:
 | 
			
		||||
                            lastLen = 2;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (it.ThingsGatewayBitConverter.Length != null && it.DataTypeEnum != DataTypeEnum.String)
 | 
			
		||||
                {
 | 
			
		||||
                    lastLen *= it.ThingsGatewayBitConverter.Length.Value;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var address = it.VariableAddress;
 | 
			
		||||
                if (address.IndexOf('.') > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    var addressSplits = address.SplitDot();
 | 
			
		||||
 | 
			
		||||
                    address = addressSplits.RemoveLast(1).ArrayToString(".");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var result = new ModbusAddress(address, (ushort)lastLen);
 | 
			
		||||
                if (result == null)
 | 
			
		||||
                {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var result = ModbusAddress.ParseFrom(address);
 | 
			
		||||
                result.ByteLength = lastLen;
 | 
			
		||||
                return result;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            //获取变量的地址
 | 
			
		||||
            var modbusAddressList = map.Keys.ToList();
 | 
			
		||||
            var modbusAddressList = map.Keys;
 | 
			
		||||
 | 
			
		||||
            //获取功能码
 | 
			
		||||
            var functionCodes = modbusAddressList.Select(t => t.ReadFunction).Distinct();
 | 
			
		||||
            foreach (var functionCode in functionCodes)
 | 
			
		||||
            {
 | 
			
		||||
                var modbusAddressSameFunList = modbusAddressList
 | 
			
		||||
                    .Where(t => t.ReadFunction == functionCode).ToList();
 | 
			
		||||
                var stationNumbers = modbusAddressSameFunList
 | 
			
		||||
                    .Select(t => t.Station).Distinct().ToList();
 | 
			
		||||
                var modbusAddressSameFunList = modbusAddressList.Where(t => t.ReadFunction == functionCode);
 | 
			
		||||
                var stationNumbers = modbusAddressSameFunList.Select(t => t.Station).Distinct();
 | 
			
		||||
                foreach (var stationNumber in stationNumbers)
 | 
			
		||||
                {
 | 
			
		||||
                    var addressList = modbusAddressSameFunList.Where(t => t.Station == stationNumber)
 | 
			
		||||
                    var addressList = modbusAddressSameFunList
 | 
			
		||||
                        .Where(t => t.Station == stationNumber)
 | 
			
		||||
                        .ToDictionary(t => t, t => map[t]);
 | 
			
		||||
                    var tempResult = LoadSourceRead(addressList, functionCode, item.Key, MaxPack);
 | 
			
		||||
                    result.AddRange(tempResult);
 | 
			
		||||
 | 
			
		||||
                    var tempResult = LoadSourceRead<T, T2>(addressList, functionCode, group.Key, maxPack);
 | 
			
		||||
                    deviceVariableSourceReads.AddRange(tempResult);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
 | 
			
		||||
        return deviceVariableSourceReads;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static List<DeviceVariableSourceRead> LoadSourceRead(Dictionary<ModbusAddress, DeviceVariableRunTime> addressList, int functionCode, int timeInterval, int MaxPack)
 | 
			
		||||
    private static List<T> LoadSourceRead<T, T2>(Dictionary<ModbusAddress, T2> addressList, int functionCode, int intervalTime, int maxPack) where T : IDeviceVariableSourceRead<IDeviceVariableRunTime>, new() where T2 : IDeviceVariableRunTime, new()
 | 
			
		||||
    {
 | 
			
		||||
        List<DeviceVariableSourceRead> sourceReads = new();
 | 
			
		||||
        List<T> sourceReads = new();
 | 
			
		||||
        //按地址和长度排序
 | 
			
		||||
        var orderByAddressAndLen = addressList.Keys.OrderBy(it => it.AddressStart + Math.Ceiling(it.Length / 2.0)).ToList();
 | 
			
		||||
        var orderByAddressEnd = addressList.Keys.OrderBy(it => it.AddressEnd);
 | 
			
		||||
        //按地址和长度排序
 | 
			
		||||
        var orderByAddress = addressList.Keys.OrderBy(it => it.AddressStart).ToList();
 | 
			
		||||
        var orderByAddressStart = addressList.Keys.OrderBy(it => it.AddressStart);
 | 
			
		||||
        //地址最小,在循环中更改
 | 
			
		||||
        var minAddress = orderByAddress.First().AddressStart;
 | 
			
		||||
        var minAddress = orderByAddressStart.First().AddressStart;
 | 
			
		||||
        //地址最大
 | 
			
		||||
        var maxAddress = orderByAddress.Last().AddressStart;
 | 
			
		||||
        var maxAddress = orderByAddressStart.Last().AddressStart;
 | 
			
		||||
 | 
			
		||||
        while (maxAddress >= minAddress)
 | 
			
		||||
        {
 | 
			
		||||
            //最大的打包长度
 | 
			
		||||
            int readLength = MaxPack;
 | 
			
		||||
            int readLength = maxPack;
 | 
			
		||||
            if (functionCode == 1 || functionCode == 2)
 | 
			
		||||
            {
 | 
			
		||||
                readLength = MaxPack * 8 * 2;
 | 
			
		||||
                readLength = maxPack * 8 * 2;
 | 
			
		||||
            }
 | 
			
		||||
            //获取当前的一组打包地址信息,
 | 
			
		||||
            var tempAddressAndLen = orderByAddressAndLen.Where(t => (t.AddressStart + (t.Length / 2.0)) <= minAddress + readLength).ToList();
 | 
			
		||||
            //起始地址
 | 
			
		||||
            var startAddress = tempAddressAndLen.OrderBy(it => it.AddressStart).First();
 | 
			
		||||
            //读取寄存器长度
 | 
			
		||||
            var sourceLen = tempAddressAndLen.Last().AddressStart + (int)Math.Ceiling(tempAddressAndLen.Last().Length / 2.0) - startAddress.AddressStart;
 | 
			
		||||
 | 
			
		||||
            DeviceVariableSourceRead sourceRead = new(timeInterval)
 | 
			
		||||
 | 
			
		||||
            //获取当前的一组打包地址信息,
 | 
			
		||||
            var tempAddressEnd = orderByAddressEnd.Where(t => t.AddressEnd <= minAddress + readLength).ToList();
 | 
			
		||||
            //起始地址
 | 
			
		||||
            var startAddress = tempAddressEnd.OrderBy(it => it.AddressStart).First();
 | 
			
		||||
            //读取寄存器长度
 | 
			
		||||
            var sourceLen = tempAddressEnd.Last().AddressEnd - startAddress.AddressStart;
 | 
			
		||||
 | 
			
		||||
            T sourceRead = new()
 | 
			
		||||
            {
 | 
			
		||||
                TimerTick = new TimerTick(intervalTime),
 | 
			
		||||
                //这里只需要根据地址排序的第一个地址,作为实际打包报文中的起始地址
 | 
			
		||||
                Address = startAddress.ToString(),
 | 
			
		||||
                VariableAddress = startAddress.ToString(),
 | 
			
		||||
                Length = sourceLen
 | 
			
		||||
            };
 | 
			
		||||
            foreach (var item in tempAddressAndLen)
 | 
			
		||||
            foreach (var item in tempAddressEnd)
 | 
			
		||||
            {
 | 
			
		||||
                var readNode = addressList[item];
 | 
			
		||||
                if ((functionCode == -1 || functionCode == 3 || functionCode == 4) &&
 | 
			
		||||
                    readNode.DataTypeEnum == DataTypeEnum.Boolean)
 | 
			
		||||
                if ((functionCode == -1 || functionCode == 3 || functionCode == 4) && readNode.DataTypeEnum == DataTypeEnum.Boolean)
 | 
			
		||||
                {
 | 
			
		||||
                    readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 16) + readNode.Index;
 | 
			
		||||
                }
 | 
			
		||||
@@ -146,15 +153,13 @@ internal static class ModbusHelper
 | 
			
		||||
                        readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 2) + readNode.Index;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                sourceRead.DeviceVariables.Add(readNode);
 | 
			
		||||
                orderByAddressAndLen.Remove(item);
 | 
			
		||||
                orderByAddress.Remove(item);
 | 
			
		||||
                sourceRead.DeviceVariableRunTimes.Add(readNode);
 | 
			
		||||
                addressList.Remove(item);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            sourceReads.Add(sourceRead);
 | 
			
		||||
            if (orderByAddressAndLen.Count > 0)
 | 
			
		||||
                minAddress = orderByAddress.First().AddressStart;
 | 
			
		||||
            if (orderByAddressEnd.Count() > 0)
 | 
			
		||||
                minAddress = orderByAddressStart.First().AddressStart;
 | 
			
		||||
            else
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -14,11 +14,10 @@
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.#ctor(System.String,System.UInt16)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.#ctor(System.String,System.Byte)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.AddressStart">
 | 
			
		||||
            <summary>
 | 
			
		||||
            读取功能码
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ReadFunction">
 | 
			
		||||
            <summary>
 | 
			
		||||
@@ -35,9 +34,29 @@
 | 
			
		||||
            写入功能码
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.Parse(System.String,System.Int32)">
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ByteLength">
 | 
			
		||||
            <summary>
 | 
			
		||||
            打包临时写入,需要读取的字节长度
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.AddressEnd">
 | 
			
		||||
            <summary>
 | 
			
		||||
            读取功能码
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.Parse(System.String)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ParseFrom(System.String,System.Byte)">
 | 
			
		||||
            <summary>
 | 
			
		||||
            解析地址
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ParseFrom(System.String,ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress)">
 | 
			
		||||
            <summary>
 | 
			
		||||
            解析地址
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusAddress.ToString">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
@@ -127,24 +146,14 @@
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.#ctor(ThingsGateway.Foundation.TcpClientEx)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.#ctor(ThingsGateway.Foundation.Sockets.TcpClient)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.CacheTimeout">
 | 
			
		||||
            <summary>
 | 
			
		||||
            组包缓存时间/ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Crc16CheckEnable">
 | 
			
		||||
            <summary>
 | 
			
		||||
            Crc校验
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.FrameTime">
 | 
			
		||||
            <summary>
 | 
			
		||||
            帧前时间ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Station">
 | 
			
		||||
            <summary>
 | 
			
		||||
            站号
 | 
			
		||||
@@ -153,10 +162,22 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.GetAddressDescription">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.SetDataAdapter">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.SetDataAdapter(System.Object)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
@@ -165,13 +186,10 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverTcp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.#ctor(TouchSocket.Sockets.UdpSession)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.#ctor(ThingsGateway.Foundation.Sockets.UdpSession)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Crc16CheckEnable">
 | 
			
		||||
@@ -179,11 +197,6 @@
 | 
			
		||||
            Crc校验
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.FrameTime">
 | 
			
		||||
            <summary>
 | 
			
		||||
            帧前时间ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Station">
 | 
			
		||||
            <summary>
 | 
			
		||||
            站号
 | 
			
		||||
@@ -192,10 +205,22 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.GetAddressDescription">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.SetDataAdapter">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.SetDataAdapter(System.Object)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
@@ -204,9 +229,6 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtuOverUdpDataHandleAdapter">
 | 
			
		||||
            <summary>
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
@@ -231,27 +253,17 @@
 | 
			
		||||
            ModbusRtu
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.#ctor(ThingsGateway.Foundation.Serial.SerialClient)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.#ctor(ThingsGateway.Foundation.Serial.SerialSession)">
 | 
			
		||||
            <summary>
 | 
			
		||||
            ModbusRtu
 | 
			
		||||
            </summary>
 | 
			
		||||
            <param name="serialClient"></param>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.CacheTimeout">
 | 
			
		||||
            <summary>
 | 
			
		||||
            组包缓存时间/ms
 | 
			
		||||
            </summary>
 | 
			
		||||
            <param name="serialSession"></param>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Crc16CheckEnable">
 | 
			
		||||
            <summary>
 | 
			
		||||
            Crc校验
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.FrameTime">
 | 
			
		||||
            <summary>
 | 
			
		||||
            帧前时间ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Station">
 | 
			
		||||
            <summary>
 | 
			
		||||
            站号
 | 
			
		||||
@@ -260,10 +272,22 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.GetAddressDescription">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Read(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.SetDataAdapter">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.SetDataAdapter(System.Object)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusRtu.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
@@ -339,7 +363,7 @@
 | 
			
		||||
            接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.#ctor(TouchSocket.Sockets.TcpService)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.#ctor(ThingsGateway.Foundation.Sockets.TcpService)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.MulStation">
 | 
			
		||||
@@ -352,13 +376,28 @@
 | 
			
		||||
            默认站点
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Dispose">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.GetAddressDescription">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Read(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.SetDataAdapter(TouchSocket.Sockets.SocketClient)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.SetDataAdapter(System.Object)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
@@ -367,10 +406,7 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Dispose(System.Boolean)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Received(TouchSocket.Sockets.SocketClient,TouchSocket.Sockets.IRequestInfo)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusServer.Received(ThingsGateway.Foundation.Sockets.SocketClient,ThingsGateway.Foundation.Core.IRequestInfo)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerDataHandleAdapter">
 | 
			
		||||
@@ -406,6 +442,11 @@
 | 
			
		||||
            当前关联的地址
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.Length">
 | 
			
		||||
            <summary>
 | 
			
		||||
            当前读写的数据长度
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusServerMessage.HeadBytesLength">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
@@ -415,19 +456,9 @@
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.#ctor(ThingsGateway.Foundation.TcpClientEx)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.#ctor(ThingsGateway.Foundation.Sockets.TcpClient)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.CacheTimeout">
 | 
			
		||||
            <summary>
 | 
			
		||||
            组包缓存时间/ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.FrameTime">
 | 
			
		||||
            <summary>
 | 
			
		||||
            帧前时间ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.IsCheckMessageId">
 | 
			
		||||
            <summary>
 | 
			
		||||
            检测事务标识符
 | 
			
		||||
@@ -441,10 +472,22 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.GetAddressDescription">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.SetDataAdapter">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.SetDataAdapter(System.Object)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
@@ -453,12 +496,9 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcpDataHandleAdapter">
 | 
			
		||||
            <summary>
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
            ModbusTcpDataHandleAdapter
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusTcpDataHandleAdapter.IsCheckMessageId">
 | 
			
		||||
@@ -494,14 +534,9 @@
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.#ctor(TouchSocket.Sockets.UdpSession)">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.#ctor(ThingsGateway.Foundation.Sockets.UdpSession)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.FrameTime">
 | 
			
		||||
            <summary>
 | 
			
		||||
            帧前时间ms
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="P:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.IsCheckMessageId">
 | 
			
		||||
            <summary>
 | 
			
		||||
            检测事务标识符
 | 
			
		||||
@@ -512,13 +547,25 @@
 | 
			
		||||
            站号
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.LoadSourceRead``2(System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.GetAddressDescription">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.Read(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.ReadAsync(System.String,System.Int32,System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.SetDataAdapter">
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.SetDataAdapter(System.Object)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.Write(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.Write(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Byte[],System.Threading.CancellationToken)">
 | 
			
		||||
@@ -527,9 +574,6 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.WriteAsync(System.String,System.Boolean[],System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdp.SendThenReturnAsync(ThingsGateway.Foundation.OperResult{System.Byte[]},System.Threading.CancellationToken)">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter">
 | 
			
		||||
            <summary>
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
@@ -549,5 +593,19 @@
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.ModbusUdpDataHandleAdapter.UnpackResponse(System.Byte[],System.Byte[])">
 | 
			
		||||
            <inheritdoc/>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="T:ThingsGateway.Foundation.Adapter.Modbus.PackHelper">
 | 
			
		||||
            <summary>
 | 
			
		||||
            PackHelper
 | 
			
		||||
            </summary>
 | 
			
		||||
        </member>
 | 
			
		||||
        <member name="M:ThingsGateway.Foundation.Adapter.Modbus.PackHelper.ModbusLoadSourceRead``2(ThingsGateway.Foundation.Core.IReadWrite,System.Collections.Generic.List{``1},System.Int32)">
 | 
			
		||||
            <summary>
 | 
			
		||||
            打包变量,添加到<see href="deviceVariableSourceReads"></see>
 | 
			
		||||
            </summary>
 | 
			
		||||
            <param name="device"></param>
 | 
			
		||||
            <param name="deviceVariables"></param>
 | 
			
		||||
            <param name="MaxPack">最大打包长度</param>
 | 
			
		||||
            <returns></returns>
 | 
			
		||||
        </member>
 | 
			
		||||
    </members>
 | 
			
		||||
</doc>
 | 
			
		||||
@@ -10,7 +10,6 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
 | 
			
		||||
@@ -10,8 +10,6 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCDA.Da;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -10,10 +10,7 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
 | 
			
		||||
 | 
			
		||||
@@ -201,7 +198,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
        OnWriteCompleted?.Invoke(itemwrite);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal OperResult AddOpcItem(OpcItem[] items)
 | 
			
		||||
    internal List<Tuple<OpcItem, int>> AddOpcItem(OpcItem[] items)
 | 
			
		||||
    {
 | 
			
		||||
        IntPtr pResults = IntPtr.Zero;
 | 
			
		||||
        IntPtr pErrors = IntPtr.Zero;
 | 
			
		||||
@@ -228,7 +225,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
            m_ItemManagement?.AddItems(items.Length, itemDefyArray, out pResults, out pErrors);
 | 
			
		||||
            IntPtr Pos = pResults;
 | 
			
		||||
            Marshal.Copy(pErrors, errors, 0, items.Length);
 | 
			
		||||
            StringBuilder stringBuilder = new();
 | 
			
		||||
            List<Tuple<OpcItem, int>> results = new();
 | 
			
		||||
            for (int j = 0; j < items.Length; j++)
 | 
			
		||||
            {
 | 
			
		||||
                if (errors[j] == 0)
 | 
			
		||||
@@ -250,21 +247,10 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    stringBuilder.AppendLine(items[j].ItemID + "添加失败,错误代码:" + errors[j]);
 | 
			
		||||
                    results.Add(Tuple.Create(items[j], errors[j]));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (stringBuilder.Length > 0)
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(stringBuilder.ToString());
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return OperResult.CreateSuccessResult();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (COMException ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
            return results;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
@@ -299,7 +285,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
    /// 组读取
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <exception cref="ExternalException"></exception>
 | 
			
		||||
    internal OperResult ReadAsync()
 | 
			
		||||
    internal void ReadAsync()
 | 
			
		||||
    {
 | 
			
		||||
        IntPtr pErrors = IntPtr.Zero;
 | 
			
		||||
        try
 | 
			
		||||
@@ -316,16 +302,11 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
                Marshal.Copy(pErrors, PErrors, 0, OpcItems.Count);
 | 
			
		||||
                if (PErrors.Any(a => a > 0))
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult("读取错误,错误代码:" + pErrors);
 | 
			
		||||
                    throw new("读取错误,错误代码:" + pErrors);
 | 
			
		||||
                }
 | 
			
		||||
                return OperResult.CreateSuccessResult();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                return new OperResult("连接无效");
 | 
			
		||||
        }
 | 
			
		||||
        catch (COMException ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
                throw new("连接无效");
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
@@ -336,7 +317,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal OperResult RemoveItem(OpcItem[] items)
 | 
			
		||||
    internal List<Tuple<OpcItem, int>> RemoveItem(OpcItem[] items)
 | 
			
		||||
    {
 | 
			
		||||
        IntPtr pErrors = IntPtr.Zero;
 | 
			
		||||
        int[] errors = new int[items.Length];
 | 
			
		||||
@@ -356,59 +337,42 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
            {
 | 
			
		||||
                Marshal.FreeCoTaskMem(pErrors);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        StringBuilder stringBuilder = new();
 | 
			
		||||
        List<Tuple<OpcItem, int>> results = new();
 | 
			
		||||
        for (int i = 0; i < errors.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            if (errors[i] != 0)
 | 
			
		||||
            {
 | 
			
		||||
                stringBuilder.AppendLine(items[i].ItemID + "移除失败,错误代码:" + errors[i]);
 | 
			
		||||
                results.Add(Tuple.Create(items[i], errors[i]));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                OpcItems.Remove(items[i]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (stringBuilder.Length > 0)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(stringBuilder.ToString());
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return OperResult.CreateSuccessResult();
 | 
			
		||||
        }
 | 
			
		||||
        return results;
 | 
			
		||||
    }
 | 
			
		||||
    internal OperResult Write(object[] values, int[] serverHandle, out int[] errors)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    internal List<Tuple<int, int>> Write(object[] values, int[] serverHandle)
 | 
			
		||||
    {
 | 
			
		||||
        IntPtr pErrors = IntPtr.Zero;
 | 
			
		||||
        errors = new int[values.Length];
 | 
			
		||||
        var errors = new int[values.Length];
 | 
			
		||||
        if (m_Async2IO != null)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                m_SyncIO.Write(values.Length, serverHandle, values, out pErrors);
 | 
			
		||||
                Marshal.Copy(pErrors, errors, 0, values.Length);
 | 
			
		||||
                StringBuilder stringBuilder = new();
 | 
			
		||||
                List<Tuple<int, int>> results = new();
 | 
			
		||||
                for (int i = 0; i < errors.Length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (errors[i] != 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        stringBuilder.AppendLine("写入错误,错误代码:" + errors[i]);
 | 
			
		||||
                        results.Add(Tuple.Create(serverHandle[i], errors[i]));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (stringBuilder.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    return new(stringBuilder.ToString());
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    return OperResult.CreateSuccessResult();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (COMException ex)
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(ex);
 | 
			
		||||
                return results;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
@@ -419,7 +383,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            return new OperResult("连接无效");
 | 
			
		||||
            throw new("连接无效");
 | 
			
		||||
    }
 | 
			
		||||
    protected virtual void Dispose(bool disposing)
 | 
			
		||||
    {
 | 
			
		||||
@@ -465,7 +429,7 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OperResult ActiveDataChanged(bool active)
 | 
			
		||||
    private void ActiveDataChanged(bool active)
 | 
			
		||||
    {
 | 
			
		||||
        IntPtr pRequestedUpdateRate = IntPtr.Zero;
 | 
			
		||||
        IntPtr hClientGroup = IntPtr.Zero;
 | 
			
		||||
@@ -484,11 +448,6 @@ internal class OpcGroup : IOPCDataCallback, IDisposable
 | 
			
		||||
                                        pDeadband,
 | 
			
		||||
                                        pLCID,
 | 
			
		||||
                                        hClientGroup);
 | 
			
		||||
            return OperResult.CreateSuccessResult();
 | 
			
		||||
        }
 | 
			
		||||
        catch (COMException ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
@@ -10,8 +10,6 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
 | 
			
		||||
@@ -51,58 +49,51 @@ internal class OpcServer : IDisposable
 | 
			
		||||
        GC.SuppressFinalize(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal OperResult<OpcGroup> AddGroup(string groupName)
 | 
			
		||||
    internal OpcGroup AddGroup(string groupName)
 | 
			
		||||
    {
 | 
			
		||||
        return AddGroup(groupName, true, 1000, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal OperResult<OpcGroup> AddGroup(string groupName, bool active, int reqUpdateRate, float deadBand)
 | 
			
		||||
    internal OpcGroup AddGroup(string groupName, bool active, int reqUpdateRate, float deadBand)
 | 
			
		||||
    {
 | 
			
		||||
        if (null == m_OpcServer || IsConnected == false)
 | 
			
		||||
            return new OperResult<OpcGroup>("未初始化连接!");
 | 
			
		||||
            throw new("未初始化连接!");
 | 
			
		||||
        OpcGroup group = new(groupName, active, reqUpdateRate, deadBand);
 | 
			
		||||
        Guid riid = typeof(IOPCItemMgt).GUID;
 | 
			
		||||
        try
 | 
			
		||||
        m_OpcServer?.AddGroup(group.Name,
 | 
			
		||||
            group.IsActive ? 1 : 0,//IsActive
 | 
			
		||||
            group.RequestUpdateRate,//RequestedUpdateRate 1000ms
 | 
			
		||||
            group.ClientGroupHandle,
 | 
			
		||||
            group.TimeBias.AddrOfPinnedObject(),
 | 
			
		||||
            group.PercendDeadBand.AddrOfPinnedObject(),
 | 
			
		||||
            group.LCID,
 | 
			
		||||
            out group.serverGroupHandle,
 | 
			
		||||
            out group.revisedUpdateRate,
 | 
			
		||||
            ref riid,
 | 
			
		||||
            out group.groupPointer);
 | 
			
		||||
        if (group.groupPointer != null)
 | 
			
		||||
        {
 | 
			
		||||
            m_OpcServer?.AddGroup(group.Name,
 | 
			
		||||
                group.IsActive ? 1 : 0,//IsActive
 | 
			
		||||
                group.RequestUpdateRate,//RequestedUpdateRate 1000ms
 | 
			
		||||
                group.ClientGroupHandle,
 | 
			
		||||
                group.TimeBias.AddrOfPinnedObject(),
 | 
			
		||||
                group.PercendDeadBand.AddrOfPinnedObject(),
 | 
			
		||||
                group.LCID,
 | 
			
		||||
                out group.serverGroupHandle,
 | 
			
		||||
                out group.revisedUpdateRate,
 | 
			
		||||
                ref riid,
 | 
			
		||||
                out group.groupPointer);
 | 
			
		||||
            if (group.groupPointer != null)
 | 
			
		||||
            {
 | 
			
		||||
                group.InitIoInterfaces(group.groupPointer);
 | 
			
		||||
                OpcGroups.Add(group);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<OpcGroup>("添加OPC组错误,OPC服务器返回null");
 | 
			
		||||
            }
 | 
			
		||||
            return OperResult.CreateSuccessResult(group);
 | 
			
		||||
            group.InitIoInterfaces(group.groupPointer);
 | 
			
		||||
            OpcGroups.Add(group);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<OpcGroup>(ex);
 | 
			
		||||
            throw new("添加OPC组错误,OPC服务器返回null");
 | 
			
		||||
        }
 | 
			
		||||
        return group;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    internal OperResult<List<BrowseElement>> Browse(string itemId = null)
 | 
			
		||||
    internal List<BrowseElement> Browse(string itemId = null)
 | 
			
		||||
    {
 | 
			
		||||
        lock (this)
 | 
			
		||||
        {
 | 
			
		||||
            if (null == m_OpcServer || IsConnected == false)
 | 
			
		||||
                return new OperResult<List<BrowseElement>>("未初始化连接!");
 | 
			
		||||
 | 
			
		||||
                throw new("未初始化连接!");
 | 
			
		||||
 | 
			
		||||
            var count = 0;
 | 
			
		||||
            var moreElements = 0;
 | 
			
		||||
@@ -118,108 +109,107 @@ internal class OpcServer : IDisposable
 | 
			
		||||
                           new PropertyID(6),
 | 
			
		||||
                           new PropertyID(101),
 | 
			
		||||
                         };
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                var server = m_OpcServer as IOPCBrowse;
 | 
			
		||||
                server.Browse(
 | 
			
		||||
                         string.IsNullOrEmpty(itemId) ? "" : itemId,
 | 
			
		||||
                     ref pContinuationPoint,
 | 
			
		||||
                     int.MaxValue,
 | 
			
		||||
                        OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL,
 | 
			
		||||
                          "",
 | 
			
		||||
                         "",
 | 
			
		||||
                         0,
 | 
			
		||||
                         1,
 | 
			
		||||
                         filterId.Length,
 | 
			
		||||
                         Interop.GetPropertyIDs(filterId),
 | 
			
		||||
                     out moreElements,
 | 
			
		||||
                     out count,
 | 
			
		||||
                     out pElements);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<List<BrowseElement>>(ex);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var server = m_OpcServer as IOPCBrowse;
 | 
			
		||||
            server.Browse(
 | 
			
		||||
                     string.IsNullOrEmpty(itemId) ? "" : itemId,
 | 
			
		||||
                 ref pContinuationPoint,
 | 
			
		||||
                 int.MaxValue,
 | 
			
		||||
                    OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL,
 | 
			
		||||
                      "",
 | 
			
		||||
                     "",
 | 
			
		||||
                     0,
 | 
			
		||||
                     1,
 | 
			
		||||
                     filterId.Length,
 | 
			
		||||
                     Interop.GetPropertyIDs(filterId),
 | 
			
		||||
                 out moreElements,
 | 
			
		||||
                 out count,
 | 
			
		||||
                 out pElements);
 | 
			
		||||
            BrowseElement[] browseElements = Interop.GetBrowseElements(ref pElements, count, true);
 | 
			
		||||
            string stringUni = Marshal.PtrToStringUni(pContinuationPoint);
 | 
			
		||||
            Marshal.FreeCoTaskMem(pContinuationPoint);
 | 
			
		||||
            this.ProcessResults(browseElements, filterId);
 | 
			
		||||
            return OperResult.CreateSuccessResult(browseElements?.ToList());
 | 
			
		||||
            return browseElements?.ToList();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal OperResult Connect()
 | 
			
		||||
    internal void Connect()
 | 
			
		||||
    {
 | 
			
		||||
        if (!string.IsNullOrEmpty(Host) && !string.IsNullOrEmpty(Name))
 | 
			
		||||
        {
 | 
			
		||||
            var info = Discovery.OpcDiscovery.GetOpcServer(Name, Host);
 | 
			
		||||
            if (!info.IsSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                return info;
 | 
			
		||||
            }
 | 
			
		||||
            object o = Comn.ComInterop.CreateInstance(info.Content.CLSID, Host);
 | 
			
		||||
            object o = Comn.ComInterop.CreateInstance(info.CLSID, Host);
 | 
			
		||||
            if (o == null)
 | 
			
		||||
            {
 | 
			
		||||
                return new(string.Format("{0}{1}无法创建com对象", info.Content.CLSID, Host));
 | 
			
		||||
                throw new(string.Format("{0}{1}无法创建com对象", info.CLSID, Host));
 | 
			
		||||
            }
 | 
			
		||||
            m_OpcServer = (IOPCServer)o;
 | 
			
		||||
            IsConnected = true;
 | 
			
		||||
            return OperResult.CreateSuccessResult();
 | 
			
		||||
        }
 | 
			
		||||
        return new("应初始化Host与Name");
 | 
			
		||||
        else
 | 
			
		||||
            throw new("应初始化Host与Name");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 服务器状态
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal OperResult<ServerStatus> GetServerStatus()
 | 
			
		||||
    internal ServerStatus GetServerStatus()
 | 
			
		||||
    {
 | 
			
		||||
        if (null == m_OpcServer || IsConnected == false)
 | 
			
		||||
            return new OperResult<ServerStatus>("未初始化连接!");
 | 
			
		||||
        IntPtr statusPtr = IntPtr.Zero;
 | 
			
		||||
        ServerStatus serverStatus = null;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (null == m_OpcServer || IsConnected == false)
 | 
			
		||||
                throw new("未初始化连接!");
 | 
			
		||||
            IntPtr statusPtr = IntPtr.Zero;
 | 
			
		||||
            m_OpcServer?.GetStatus(out statusPtr);
 | 
			
		||||
            OPCSERVERSTATUS status;
 | 
			
		||||
            ServerStatus = new ServerStatus();
 | 
			
		||||
            if (statusPtr != IntPtr.Zero)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
 | 
			
		||||
                object o = Marshal.PtrToStructure(statusPtr, typeof(OPCSERVERSTATUS));
 | 
			
		||||
 | 
			
		||||
                Marshal.FreeCoTaskMem(statusPtr);
 | 
			
		||||
 | 
			
		||||
                if (o != null)
 | 
			
		||||
                {
 | 
			
		||||
                    object o = Marshal.PtrToStructure(statusPtr, typeof(OPCSERVERSTATUS));
 | 
			
		||||
                    if (null != o)
 | 
			
		||||
                    {
 | 
			
		||||
                        status = (OPCSERVERSTATUS)o;
 | 
			
		||||
                        ServerStatus.Version = status.wMajorVersion.ToString() + "." + status.wMinorVersion.ToString() + "." + status.wBuildNumber.ToString();
 | 
			
		||||
                        ServerStatus.ServerState = status.dwServerState;
 | 
			
		||||
                        ServerStatus.StartTime = Comn.Convert.FileTimeToDateTime(status.ftStartTime);
 | 
			
		||||
                        ServerStatus.CurrentTime = Comn.Convert.FileTimeToDateTime(status.ftCurrentTime);
 | 
			
		||||
                        ServerStatus.LastUpdateTime = Comn.Convert.FileTimeToDateTime(status.ftLastUpdateTime);
 | 
			
		||||
                        ServerStatus.VendorInfo = status.szVendorInfo;
 | 
			
		||||
                    }
 | 
			
		||||
                    status = (OPCSERVERSTATUS)o;
 | 
			
		||||
                    serverStatus = new();
 | 
			
		||||
                    serverStatus.Version = status.wMajorVersion.ToString() + "." + status.wMinorVersion.ToString() + "." + status.wBuildNumber.ToString();
 | 
			
		||||
                    serverStatus.ServerState = status.dwServerState;
 | 
			
		||||
                    serverStatus.StartTime = Comn.Convert.FileTimeToDateTime(status.ftStartTime);
 | 
			
		||||
                    serverStatus.CurrentTime = Comn.Convert.FileTimeToDateTime(status.ftCurrentTime);
 | 
			
		||||
                    serverStatus.LastUpdateTime = Comn.Convert.FileTimeToDateTime(status.ftLastUpdateTime);
 | 
			
		||||
                    serverStatus.VendorInfo = status.szVendorInfo;
 | 
			
		||||
                    IsConnected = true;
 | 
			
		||||
                    return OperResult.CreateSuccessResult(ServerStatus);
 | 
			
		||||
 | 
			
		||||
                    return serverStatus;
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    IsConnected = false;
 | 
			
		||||
                    return new OperResult<ServerStatus>(ex);
 | 
			
		||||
                    throw new("未知错误");
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                IsConnected = false;
 | 
			
		||||
                return new OperResult<ServerStatus>("获取状态失败");
 | 
			
		||||
                throw new("未知错误");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            IsConnected = false;
 | 
			
		||||
            return new OperResult<ServerStatus>(ex);
 | 
			
		||||
            if (serverStatus != null)
 | 
			
		||||
                IsConnected = true;
 | 
			
		||||
            else
 | 
			
		||||
                IsConnected = false;
 | 
			
		||||
            ServerStatus = serverStatus;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal void RemoveGroup(OpcGroup group)
 | 
			
		||||
@@ -10,15 +10,11 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCDA.Discovery;
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// OpcDiscovery
 | 
			
		||||
@@ -38,58 +34,58 @@ public class OpcDiscovery
 | 
			
		||||
    /// <param name="serverName"></param>
 | 
			
		||||
    /// <param name="host"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static OperResult<ServerInfo> GetOpcServer(string serverName, string host)
 | 
			
		||||
    internal static ServerInfo GetOpcServer(string serverName, string host)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if (string.IsNullOrEmpty(serverName))
 | 
			
		||||
        {
 | 
			
		||||
            throw new("检索失败,需提供OPCName");
 | 
			
		||||
        }
 | 
			
		||||
        ServerInfo result = null;
 | 
			
		||||
        ServerInfo[] serverInfos = null;
 | 
			
		||||
        object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host);
 | 
			
		||||
        if (o_Server == null)
 | 
			
		||||
            throw new("检索失败,请检查是否安装OPC Runtime");
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(serverName))
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult<ServerInfo>("检索失败,需提供OPCName");
 | 
			
		||||
            }
 | 
			
		||||
            ServerInfo result = null;
 | 
			
		||||
            ServerInfo[] serverInfos = null;
 | 
			
		||||
            object o_Server = Comn.ComInterop.CreateInstance(OPCEnumCLSID, host);
 | 
			
		||||
            if (o_Server == null)
 | 
			
		||||
                return new OperResult<ServerInfo>("检索失败,请检查是否安装OPC Runtime");
 | 
			
		||||
            Guid catid = CATID_OPC_DA20;
 | 
			
		||||
 | 
			
		||||
            //两种方式,兼容国产部分OPCServer不支持IOPCServerList2的情况
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                Guid catid = CATID_OPC_DA20;
 | 
			
		||||
 | 
			
		||||
                //两种方式,兼容国产部分OPCServer不支持IOPCServerList2的情况
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    IOPCServerList2 m_server2 = (IOPCServerList2)o_Server;
 | 
			
		||||
                    GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server2, catid);
 | 
			
		||||
                    if (result == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        IOPCServerList m_server = (IOPCServerList)o_Server;
 | 
			
		||||
                        GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception)
 | 
			
		||||
                IOPCServerList2 m_server2 = (IOPCServerList2)o_Server;
 | 
			
		||||
                GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server2, catid);
 | 
			
		||||
                if (result == null)
 | 
			
		||||
                {
 | 
			
		||||
                    IOPCServerList m_server = (IOPCServerList)o_Server;
 | 
			
		||||
                    GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid);
 | 
			
		||||
                }
 | 
			
		||||
                if (result == null)
 | 
			
		||||
                {
 | 
			
		||||
                    return new OperResult<ServerInfo>($"无法创建OPCServer连接,请检查OPC名称是否一致,以下为Host{host}中的OPC列表:"
 | 
			
		||||
                        + Environment.NewLine +
 | 
			
		||||
                      JToken.Parse(serverInfos.ToJson()).ToString()
 | 
			
		||||
                        );
 | 
			
		||||
                }
 | 
			
		||||
                return OperResult.CreateSuccessResult(result);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                Comn.ComInterop.RealseComServer(o_Server);
 | 
			
		||||
                o_Server = null;
 | 
			
		||||
                IOPCServerList m_server = (IOPCServerList)o_Server;
 | 
			
		||||
                GetIOPCServerList(ref result, ref serverInfos, serverName, host, m_server, catid);
 | 
			
		||||
            }
 | 
			
		||||
            if (result == null)
 | 
			
		||||
            {
 | 
			
		||||
                StringBuilder sb = new StringBuilder();
 | 
			
		||||
                foreach (var item in serverInfos)
 | 
			
		||||
                {
 | 
			
		||||
                    sb.AppendLine(item.ToString());
 | 
			
		||||
                }
 | 
			
		||||
                throw new($"无法创建OPCServer连接,请检查OPC名称是否一致,以下为{host}中的OPC列表:"
 | 
			
		||||
                    + Environment.NewLine +
 | 
			
		||||
                  sb.ToString()
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<ServerInfo>(ex);
 | 
			
		||||
            Comn.ComInterop.RealseComServer(o_Server);
 | 
			
		||||
            o_Server = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void GetIOPCServerList(ref ServerInfo result, ref ServerInfo[] serverInfos, string serverName, string host, IOPCServerList m_server, Guid catid)
 | 
			
		||||
@@ -246,4 +242,15 @@ internal class ServerInfo
 | 
			
		||||
    internal string Host { get; set; } = string.Empty;
 | 
			
		||||
    internal string ProgID { get; set; } = string.Empty;
 | 
			
		||||
    internal string VerIndProgID { get; set; } = string.Empty;
 | 
			
		||||
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
        StringBuilder stringBuilder = new StringBuilder();
 | 
			
		||||
        stringBuilder.AppendLine($"{nameof(CLSID)}:{CLSID}");
 | 
			
		||||
        stringBuilder.AppendLine($"{nameof(Description)}:{Description}");
 | 
			
		||||
        stringBuilder.AppendLine($"{nameof(Host)}:{Host}");
 | 
			
		||||
        stringBuilder.AppendLine($"{nameof(ProgID)}:{ProgID}");
 | 
			
		||||
        stringBuilder.AppendLine($"{nameof(VerIndProgID)}:{VerIndProgID}");
 | 
			
		||||
        return stringBuilder.ToString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,9 +10,6 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCDA;
 | 
			
		||||
 | 
			
		||||
internal static class CollectionExtensions
 | 
			
		||||
@@ -23,11 +20,30 @@ internal static class CollectionExtensions
 | 
			
		||||
    /// <typeparam name="T"></typeparam>
 | 
			
		||||
    /// <param name="this"></param>
 | 
			
		||||
    /// <param name="where"></param>
 | 
			
		||||
    public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
 | 
			
		||||
    internal static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var obj in @this.Where(where).ToList())
 | 
			
		||||
        {
 | 
			
		||||
            @this.Remove(obj);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 将项目列表分解为特定大小的块
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T"></typeparam>
 | 
			
		||||
    /// <param name="source"></param>
 | 
			
		||||
    /// <param name="chunksize"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static List<List<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
 | 
			
		||||
    {
 | 
			
		||||
        var pos = 0;
 | 
			
		||||
        List<List<T>> n = new();
 | 
			
		||||
        while (source.Skip(pos).Any())
 | 
			
		||||
        {
 | 
			
		||||
            n.Add(source.Skip(pos).Take(chunksize).ToList());
 | 
			
		||||
            pos += chunksize;
 | 
			
		||||
        }
 | 
			
		||||
        return n;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,123 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
 | 
			
		||||
//  CSDN博客:https://blog.csdn.net/qq_40374647
 | 
			
		||||
//  哔哩哔哩视频:https://space.bilibili.com/94253567
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/RRQM_Home
 | 
			
		||||
//  Github源代码仓库:https://github.com/RRQM
 | 
			
		||||
//  API首页:http://rrqm_home.gitee.io/touchsocket/
 | 
			
		||||
//  交流QQ群:234762506
 | 
			
		||||
//  感谢您的下载和使用
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCDA;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DictionaryExtension
 | 
			
		||||
/// </summary>
 | 
			
		||||
internal static class DictionaryExtension
 | 
			
		||||
{
 | 
			
		||||
    #region 字典扩展
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 移除满足条件的项目。
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="TKey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="pairs"></param>
 | 
			
		||||
    /// <param name="func"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
 | 
			
		||||
    {
 | 
			
		||||
        var list = new List<TKey>();
 | 
			
		||||
        foreach (var item in pairs)
 | 
			
		||||
        {
 | 
			
		||||
            if (func?.Invoke(item) == true)
 | 
			
		||||
            {
 | 
			
		||||
                list.Add(item.Key);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var count = 0;
 | 
			
		||||
        foreach (var item in list)
 | 
			
		||||
        {
 | 
			
		||||
            if (pairs.TryRemove(item, out _))
 | 
			
		||||
            {
 | 
			
		||||
                count++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 尝试添加
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="Tkey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="dictionary"></param>
 | 
			
		||||
    /// <param name="tkey"></param>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
 | 
			
		||||
    {
 | 
			
		||||
        if (dictionary.ContainsKey(tkey))
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        dictionary.Add(tkey, value);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 尝试添加
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="Tkey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="dictionary"></param>
 | 
			
		||||
    /// <param name="tkey"></param>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
 | 
			
		||||
    {
 | 
			
		||||
        if (dictionary.ContainsKey(tkey))
 | 
			
		||||
        {
 | 
			
		||||
            dictionary[tkey] = value;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            dictionary.Add(tkey, value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取值。如果键不存在,则返回默认值。
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="Tkey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="dictionary"></param>
 | 
			
		||||
    /// <param name="tkey"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
 | 
			
		||||
    {
 | 
			
		||||
        return dictionary.TryGetValue(tkey, out var value) ? value : default;
 | 
			
		||||
    }
 | 
			
		||||
    #endregion 字典扩展
 | 
			
		||||
}
 | 
			
		||||
@@ -13,5 +13,5 @@
 | 
			
		||||
global using System;
 | 
			
		||||
global using System.Collections.Generic;
 | 
			
		||||
global using System.Linq;
 | 
			
		||||
global using System.Threading.Tasks;
 | 
			
		||||
global using System.Threading;
 | 
			
		||||
 | 
			
		||||
@@ -10,20 +10,12 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Timers;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.OPCDA.Da;
 | 
			
		||||
using ThingsGateway.Foundation.Adapter.OPCDA.Rcw;
 | 
			
		||||
using ThingsGateway.Foundation.Extension.Generic;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
 | 
			
		||||
using Timer = System.Timers.Timer;
 | 
			
		||||
 | 
			
		||||
@@ -38,30 +30,29 @@ public delegate void DataChangedEventHandler(List<ItemReadResult> values);
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// OPCDAClient
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class OPCDAClient : DisposableObject
 | 
			
		||||
public class OPCDAClient : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    private readonly ILog _logger;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// LogAction
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private readonly Action<byte, object, string, Exception> _logAction;
 | 
			
		||||
 | 
			
		||||
    private readonly EasyLock checkLock = new();
 | 
			
		||||
    private readonly object checkLock = new();
 | 
			
		||||
 | 
			
		||||
    private Timer checkTimer;
 | 
			
		||||
 | 
			
		||||
    private bool FirstConnect;
 | 
			
		||||
 | 
			
		||||
    private int IsExit = 1;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 当前保存的需订阅列表
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private Dictionary<string, List<OpcItem>> ItemDicts = new();
 | 
			
		||||
 | 
			
		||||
    private OpcServer m_server;
 | 
			
		||||
 | 
			
		||||
    private bool publicConnect;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="logger"></param>
 | 
			
		||||
    public OPCDAClient(ILog logger)
 | 
			
		||||
    public OPCDAClient(Action<byte, object, string, Exception> logAction)
 | 
			
		||||
    {
 | 
			
		||||
#if (NET6_0_OR_GREATER)
 | 
			
		||||
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
 | 
			
		||||
@@ -69,7 +60,7 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
            throw new NotSupportedException("不支持非windows系统");
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
        _logger = logger;
 | 
			
		||||
        _logAction = logAction;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -87,68 +78,56 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public OPCNode OPCNode { get; private set; }
 | 
			
		||||
    private List<OpcGroup> Groups => m_server.OpcGroups;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 添加节点,需要在连接成功后执行
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="items">组名称/变量节点,注意每次添加的组名称不能相同</param>
 | 
			
		||||
    public OperResult AddItems(Dictionary<string, List<OpcItem>> items)
 | 
			
		||||
    public void AddItems(Dictionary<string, List<OpcItem>> items)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        if (IsExit == 1) throw new("对象已释放");
 | 
			
		||||
        foreach (var item in items)
 | 
			
		||||
        {
 | 
			
		||||
            if (IsExit == 1) return new("对象已释放");
 | 
			
		||||
            StringBuilder stringBuilder = new();
 | 
			
		||||
            foreach (var item in items)
 | 
			
		||||
            if (IsExit == 1) throw new("对象已释放");
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (IsExit == 1) return new("对象已释放");
 | 
			
		||||
                var subscription = m_server.AddGroup(item.Key, true, OPCNode.UpdateRate, OPCNode.DeadBand);
 | 
			
		||||
                if (subscription.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    subscription.Content.ActiveSubscribe = OPCNode.ActiveSubscribe;
 | 
			
		||||
                    subscription.Content.OnDataChanged += Subscription_OnDataChanged;
 | 
			
		||||
                    subscription.Content.OnReadCompleted += Subscription_OnDataChanged;
 | 
			
		||||
                subscription.ActiveSubscribe = OPCNode.ActiveSubscribe;
 | 
			
		||||
                subscription.OnDataChanged += Subscription_OnDataChanged;
 | 
			
		||||
                subscription.OnReadCompleted += Subscription_OnDataChanged;
 | 
			
		||||
 | 
			
		||||
                    var result = subscription.Content.AddOpcItem(item.Value.ToArray());
 | 
			
		||||
                    if (!result.IsSuccess)
 | 
			
		||||
                var result = subscription.AddOpcItem(item.Value.ToArray());
 | 
			
		||||
                StringBuilder stringBuilder = new StringBuilder();
 | 
			
		||||
                if (result.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var item1 in result)
 | 
			
		||||
                    {
 | 
			
		||||
                        stringBuilder.AppendLine("添加变量失败" + result.Message);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        ItemDicts.AddOrUpdate(item.Key, item.Value);
 | 
			
		||||
                        _logger?.Debug($"添加成功");
 | 
			
		||||
                        stringBuilder.Append($"{item1.Item1.ItemID}:{item1.Item2}");
 | 
			
		||||
                    }
 | 
			
		||||
                    _logAction?.Invoke(3, this, $"添加变量失败:{stringBuilder}", null);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    stringBuilder.AppendLine("添加组失败" + subscription.Message);
 | 
			
		||||
                    ItemDicts.AddOrUpdate(item.Key, item.Value.Where(a => !result.Select(b => b.Item1).Contains(a)).ToList());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            for (int i = 0; i < Groups?.Count; i++)
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                var group = Groups[i];
 | 
			
		||||
                if (group != null)
 | 
			
		||||
                {
 | 
			
		||||
                    if (group.OpcItems.Count == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        ItemDicts.Remove(group.Name);
 | 
			
		||||
                        m_server.RemoveGroup(group);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (stringBuilder.Length > 0)
 | 
			
		||||
            {
 | 
			
		||||
                return new(stringBuilder.ToString());
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return OperResult.CreateSuccessResult();
 | 
			
		||||
                _logAction?.Invoke(3, this, $"添加组失败:{ex.Message}", ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        for (int i = 0; i < Groups?.Count; i++)
 | 
			
		||||
        {
 | 
			
		||||
            return new(ex);
 | 
			
		||||
            var group = Groups[i];
 | 
			
		||||
            if (group != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (group.OpcItems.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    ItemDicts.Remove(group.Name);
 | 
			
		||||
                    m_server.RemoveGroup(group);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -159,8 +138,7 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
    public Dictionary<string, List<OpcItem>> AddItemsWithSave(List<string> items)
 | 
			
		||||
    {
 | 
			
		||||
        int i = 0;
 | 
			
		||||
        ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)
 | 
			
		||||
          ).ChunkTrivialBetter(OPCNode.GroupSize).ToDictionary(a => "default" + (i++));
 | 
			
		||||
        ItemDicts = items.ToList().ConvertAll(o => new OpcItem(o)).ChunkTrivialBetter(OPCNode.GroupSize).ToDictionary(a => "default" + (i++));
 | 
			
		||||
        return ItemDicts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -169,9 +147,9 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void Connect()
 | 
			
		||||
    {
 | 
			
		||||
        publicConnect = true;
 | 
			
		||||
        Interlocked.CompareExchange(ref IsExit, 0, 1);
 | 
			
		||||
        PrivateConnect();
 | 
			
		||||
        FirstConnect = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -183,12 +161,26 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
        PrivateDisconnect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            PrivateDisconnect();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            _logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
        Interlocked.CompareExchange(ref IsExit, 1, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 浏览节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="itemId"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public OperResult<List<BrowseElement>> GetBrowseElements(string itemId = null)
 | 
			
		||||
    public List<BrowseElement> GetBrowseElements(string itemId = null)
 | 
			
		||||
    {
 | 
			
		||||
        return this.m_server?.Browse(itemId);
 | 
			
		||||
    }
 | 
			
		||||
@@ -197,7 +189,7 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
    /// 获取服务状态
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public OperResult<ServerStatus> GetServerStatus()
 | 
			
		||||
    public ServerStatus GetServerStatus()
 | 
			
		||||
    {
 | 
			
		||||
        return this.m_server?.GetServerStatus();
 | 
			
		||||
    }
 | 
			
		||||
@@ -211,11 +203,18 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
        if (node != null)
 | 
			
		||||
            OPCNode = node;
 | 
			
		||||
        checkTimer?.Stop();
 | 
			
		||||
        checkTimer?.SafeDispose();
 | 
			
		||||
        checkTimer?.Dispose();
 | 
			
		||||
        checkTimer = new Timer(OPCNode.CheckRate * 60 * 1000);
 | 
			
		||||
        checkTimer.Elapsed += CheckTimer_Elapsed;
 | 
			
		||||
        checkTimer.Start();
 | 
			
		||||
        m_server?.SafeDispose();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            m_server?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            _logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
        m_server = new OpcServer(OPCNode.OPCName, OPCNode.OPCIP);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -224,32 +223,18 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="groupName">组名称,值为null时读取全部组</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public OperResult ReadItemsWithGroup(string groupName = null)
 | 
			
		||||
    public void ReadItemsWithGroup(string groupName = null)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        PrivateConnect();
 | 
			
		||||
        {
 | 
			
		||||
            if (PrivateConnect())
 | 
			
		||||
            var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups;
 | 
			
		||||
            foreach (var group in groups)
 | 
			
		||||
            {
 | 
			
		||||
                var groups = groupName != null ? Groups.Where(a => a.Name == groupName) : Groups;
 | 
			
		||||
                foreach (var group in groups)
 | 
			
		||||
                if (group.OpcItems.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    if (group.OpcItems.Count > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        return group.ReadAsync();
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        return new OperResult("不存在任何变量");
 | 
			
		||||
                    }
 | 
			
		||||
                    group.ReadAsync();
 | 
			
		||||
                }
 | 
			
		||||
                return new OperResult("不存在任何变量");
 | 
			
		||||
            }
 | 
			
		||||
            return new OperResult("未初始化连接");
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -263,23 +248,11 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
        {
 | 
			
		||||
            if (IsExit == 1) return;
 | 
			
		||||
            var opcGroups = Groups.Where(it => it.OpcItems.Any(a => a.ItemID == item));
 | 
			
		||||
            if (!opcGroups.Any())
 | 
			
		||||
            {
 | 
			
		||||
                _logger.Warning("找不到变量" + item);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var opcGroup in opcGroups)
 | 
			
		||||
            {
 | 
			
		||||
                var tag = opcGroup.OpcItems.Where(a => item == a.ItemID);
 | 
			
		||||
                var result = opcGroup.RemoveItem(tag.ToArray());
 | 
			
		||||
                if (!result.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    _logger.Warning($"移除变量{item}-" + result.Message);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    _logger?.Debug($"移除变量{item}成功");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (opcGroup.OpcItems.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    opcGroup.OnDataChanged -= Subscription_OnDataChanged;
 | 
			
		||||
@@ -288,7 +261,7 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    ItemDicts[opcGroup.Name].RemoveWhere(a => tag.Contains(a));
 | 
			
		||||
                    ItemDicts[opcGroup.Name].RemoveWhere(a => tag.Contains(a) && !result.Select(b => b.Item1).Contains(a));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -302,83 +275,94 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 写入值
 | 
			
		||||
    /// 批量写入值
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="valueName">写入</param>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public OperResult WriteItem(string valueName, JToken value)
 | 
			
		||||
    public Dictionary<string, Tuple<bool, string>> WriteItem(Dictionary<string, object> writeInfos)
 | 
			
		||||
    {
 | 
			
		||||
        if (PrivateConnect())
 | 
			
		||||
        PrivateConnect();
 | 
			
		||||
        Dictionary<string, Tuple<bool, string>> results = new();
 | 
			
		||||
 | 
			
		||||
        var valueGroup = writeInfos.GroupBy(itemId =>
 | 
			
		||||
          {
 | 
			
		||||
              var group = Groups.FirstOrDefault(it => it.OpcItems.Any(a => a.ItemID == itemId.Key));
 | 
			
		||||
              return group;
 | 
			
		||||
          }).ToList();
 | 
			
		||||
 | 
			
		||||
        foreach (var item1 in valueGroup)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var group = Groups.FirstOrDefault(it => it.OpcItems.Any(a => a.ItemID == valueName));
 | 
			
		||||
                if (group == null)
 | 
			
		||||
                    return new OperResult("不存在该变量" + valueName);
 | 
			
		||||
                var item = group.OpcItems.Where(it => it.ItemID == valueName).FirstOrDefault();
 | 
			
		||||
                int[] serverHandle = new int[1] { item.ServerHandle };
 | 
			
		||||
                int[] PErrors = new int[1];
 | 
			
		||||
                var jtoken = value;
 | 
			
		||||
                var rank = jtoken.CalculateActualValueRank();
 | 
			
		||||
                object rawWriteValue;
 | 
			
		||||
                switch (rank)
 | 
			
		||||
                if (item1.Key == null)
 | 
			
		||||
                {
 | 
			
		||||
                    case -1:
 | 
			
		||||
                        rawWriteValue = ((JValue)jtoken).Value;
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        var jarray = ((JArray)jtoken);
 | 
			
		||||
                        rawWriteValue = jarray.Select(j => (object)j).ToArray();
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                object[] Value = new object[1] { rawWriteValue };
 | 
			
		||||
                var result = group.Write(Value, serverHandle, out PErrors);
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(ex.Message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return new OperResult();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void Dispose(bool disposing)
 | 
			
		||||
    {
 | 
			
		||||
        PrivateDisconnect();
 | 
			
		||||
        base.Dispose(disposing);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void CheckTimer_Elapsed(object sender, ElapsedEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        if (checkLock.IsWaitting) return;
 | 
			
		||||
        checkLock.Wait();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (IsExit == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var status = m_server.GetServerStatus();
 | 
			
		||||
                if (status.IsSuccess)
 | 
			
		||||
                {
 | 
			
		||||
                    _logger?.Trace(OPCNode.ToString() + "OPC状态检查正常!");
 | 
			
		||||
                    foreach (var item2 in item1)
 | 
			
		||||
                    {
 | 
			
		||||
                        results.AddOrUpdate(item2.Key, Tuple.Create(true, $"不存在该变量{item2.Key}"));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    if (IsExit == 0 && FirstConnect)
 | 
			
		||||
                    List<int> serverHandles = new();
 | 
			
		||||
                    Dictionary<int, OpcItem> handleItems = new();
 | 
			
		||||
                    List<object> values = new();
 | 
			
		||||
                    foreach (var item2 in item1)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (PrivateConnect())
 | 
			
		||||
                        {
 | 
			
		||||
                            _logger?.Warning(OPCNode.ToString() + "OPC重新链接成功!");
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                        var opcItem = item1.Key.OpcItems.Where(it => it.ItemID == item2.Key).FirstOrDefault();
 | 
			
		||||
                        serverHandles.Add(opcItem.ServerHandle);
 | 
			
		||||
                        handleItems.AddOrUpdate(opcItem.ServerHandle, opcItem);
 | 
			
		||||
                        var rawWriteValue = item2.Value;
 | 
			
		||||
                        values.Add(rawWriteValue);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var result = item1.Key.Write(values.ToArray(), serverHandles.ToArray());
 | 
			
		||||
                    var data = item1.ToList();
 | 
			
		||||
                    foreach (var item2 in result)
 | 
			
		||||
                    {
 | 
			
		||||
                        results.AddOrUpdate(handleItems[item2.Item1].ItemID, Tuple.Create(true, $"错误代码{item2.Item2}"));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var item2 in item1)
 | 
			
		||||
                {
 | 
			
		||||
                    results.AddOrUpdate(item2.Key, Tuple.Create(false, $"成功"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                var keys = writeInfos.Keys.ToList();
 | 
			
		||||
                foreach (var item in keys)
 | 
			
		||||
                {
 | 
			
		||||
                    results.AddOrUpdate(item, Tuple.Create(true, ex.Message));
 | 
			
		||||
                }
 | 
			
		||||
                return results;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return results;
 | 
			
		||||
    }
 | 
			
		||||
    private void CheckTimer_Elapsed(object sender, ElapsedEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        lock (checkLock)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (IsExit == 0)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var status = m_server.GetServerStatus();
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    if (IsExit == 0 && publicConnect)
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            PrivateConnect();
 | 
			
		||||
                            _logAction?.Invoke(1, this, $"重新链接成功", null);
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (Exception ex)
 | 
			
		||||
                        {
 | 
			
		||||
                            _logAction?.Invoke(3, this, $"重新链接失败:{ex.Message}", ex);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
@@ -387,103 +371,81 @@ public class OPCDAClient : DisposableObject
 | 
			
		||||
                timeer.Enabled = false;
 | 
			
		||||
                timeer.Stop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        finally { checkLock.Release(); }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void PrivateAddItems()
 | 
			
		||||
    {
 | 
			
		||||
        var result = AddItems(ItemDicts);
 | 
			
		||||
        if (!result.IsSuccess)
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            _logger.Warning(result.Message);
 | 
			
		||||
            AddItems(ItemDicts);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            _logAction?.Invoke(3, this, $"添加点位失败:{ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private bool PrivateConnect()
 | 
			
		||||
    private void PrivateConnect()
 | 
			
		||||
    {
 | 
			
		||||
        lock (this)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
 | 
			
		||||
            if (m_server?.IsConnected == true)
 | 
			
		||||
            {
 | 
			
		||||
                if (m_server?.IsConnected == true)
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var status = m_server.GetServerStatus();
 | 
			
		||||
                    if (!status.IsSuccess)
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var status1 = m_server.GetServerStatus();
 | 
			
		||||
                        if (!status1.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            _logger?.Error(status1.Message);
 | 
			
		||||
                            //失败重新连接
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
                                //disconnect();
 | 
			
		||||
                                Init(OPCNode);
 | 
			
		||||
                                _logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 正在连接");
 | 
			
		||||
                                var result = m_server?.Connect();
 | 
			
		||||
                                if (result.IsSuccess)
 | 
			
		||||
                                {
 | 
			
		||||
                                    _logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 连接成功");
 | 
			
		||||
                                    PrivateAddItems();
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    _logger?.Error(result.Message);
 | 
			
		||||
                                    return IsConnected;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            catch (Exception ex2)
 | 
			
		||||
                            {
 | 
			
		||||
                                _logger?.Exception(ex2);
 | 
			
		||||
                                return IsConnected;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    //disconnect();
 | 
			
		||||
                    Init(OPCNode);
 | 
			
		||||
                    _logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 正在连接");
 | 
			
		||||
                    var result = m_server?.Connect();
 | 
			
		||||
                    if (result.IsSuccess)
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 连接成功");
 | 
			
		||||
 | 
			
		||||
                        Init(OPCNode);
 | 
			
		||||
                        m_server?.Connect();
 | 
			
		||||
                        _logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
 | 
			
		||||
                        PrivateAddItems();
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger?.Error(result.Message);
 | 
			
		||||
                        return IsConnected;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _logger?.Exception(OPCNode.ToString(), ex);
 | 
			
		||||
                return false;
 | 
			
		||||
                Init(OPCNode);
 | 
			
		||||
                m_server?.Connect();
 | 
			
		||||
                _logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 连接成功", null);
 | 
			
		||||
                PrivateAddItems();
 | 
			
		||||
            }
 | 
			
		||||
            return IsConnected;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void PrivateDisconnect()
 | 
			
		||||
    {
 | 
			
		||||
        lock (this)
 | 
			
		||||
        {
 | 
			
		||||
            if (IsConnected)
 | 
			
		||||
                _logger?.Trace($"{m_server.Host + " - " + m_server.Name} - 断开连接");
 | 
			
		||||
            checkTimer.Enabled = false;
 | 
			
		||||
            checkTimer.Stop();
 | 
			
		||||
                _logAction?.Invoke(1, this, $"{m_server.Host} - {m_server.Name} - 断开连接", null);
 | 
			
		||||
            if (checkTimer != null)
 | 
			
		||||
            {
 | 
			
		||||
                checkTimer.Enabled = false;
 | 
			
		||||
                checkTimer.Stop();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                m_server?.SafeDispose();
 | 
			
		||||
                m_server?.Dispose();
 | 
			
		||||
                m_server = null;
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                _logger?.Exception(ToString(), ex);
 | 
			
		||||
                _logAction?.Invoke(3, this, $"连接释放失败:{ex.Message}", ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -21,7 +21,7 @@ public class OPCNode
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 是否订阅
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("ActiveSubscribe")]
 | 
			
		||||
    [Description("订阅")]
 | 
			
		||||
    public bool ActiveSubscribe { get; set; } = true;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 内部检测重连间隔/min
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
<Project Sdk="Microsoft.NET.Sdk">
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
</Project>
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
@@ -34,6 +34,11 @@ public class OPCNode
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("登录密码")]
 | 
			
		||||
    public string Password { get; set; }
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 检查域
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("检查域")]
 | 
			
		||||
    public bool CheckDomain { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 更新间隔
 | 
			
		||||
@@ -65,6 +70,11 @@ public class OPCNode
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("安全策略")]
 | 
			
		||||
    public bool IsUseSecurity { get; set; } = false;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 加载服务端数据类型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [Description("加载服务端数据类型")]
 | 
			
		||||
    public bool LoadType { get; set; } = true;
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
    {
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
@@ -17,13 +17,6 @@ using Opc.Ua.Client;
 | 
			
		||||
using Opc.Ua.Client.ComplexTypes;
 | 
			
		||||
using Opc.Ua.Configuration;
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Extension;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//修改自https://github.com/dathlin/OpcUaHelper 与OPC基金会net库
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +29,7 @@ public delegate void DataChangedEventHandler((VariableNode variableNode, DataVal
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// OPCUAClient
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class OPCUAClient : DisposableObject
 | 
			
		||||
public class OPCUAClient : IDisposable
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    #region 属性,变量等
 | 
			
		||||
@@ -55,39 +48,32 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public List<string> Variables = new();
 | 
			
		||||
 | 
			
		||||
    private readonly Action<byte, object, string, Exception> _logAction;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 当前的变量名称/OPC变量节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private readonly Dictionary<string, VariableNode> _variableDicts = new();
 | 
			
		||||
    private readonly EasyLock checkLock = new();
 | 
			
		||||
    private readonly object checkLock = new();
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 当前的订阅组,组名称/组
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private readonly Dictionary<string, Subscription> dic_subscriptions = new();
 | 
			
		||||
 | 
			
		||||
    private readonly ILog Log;
 | 
			
		||||
    private readonly ApplicationInstance m_application = new();
 | 
			
		||||
 | 
			
		||||
    private readonly ApplicationConfiguration m_configuration;
 | 
			
		||||
 | 
			
		||||
    private SessionReconnectHandler m_reConnectHandler;
 | 
			
		||||
 | 
			
		||||
    private ISession m_session;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 默认的构造函数,实例化一个新的OPC UA类
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public OPCUAClient(ILog log)
 | 
			
		||||
    public OPCUAClient(Action<byte, object, string, Exception> log)
 | 
			
		||||
    {
 | 
			
		||||
        Log = log;
 | 
			
		||||
        _logAction = log;
 | 
			
		||||
        var certificateValidator = new CertificateValidator();
 | 
			
		||||
        certificateValidator.CertificateValidation += CertificateValidation;
 | 
			
		||||
        SecurityConfiguration securityConfigurationcv = new()
 | 
			
		||||
        {
 | 
			
		||||
            UseValidatedCertificates = true,
 | 
			
		||||
            AutoAcceptUntrustedCertificates = true,//自动接受证书
 | 
			
		||||
            RejectSHA1SignedCertificates = false,
 | 
			
		||||
            MinimumCertificateKeySize = 1024,
 | 
			
		||||
        };
 | 
			
		||||
        certificateValidator.Update(securityConfigurationcv);
 | 
			
		||||
 | 
			
		||||
        // 构建应用程序配置
 | 
			
		||||
        m_configuration = new ApplicationConfiguration
 | 
			
		||||
@@ -109,7 +95,8 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
 | 
			
		||||
            SecurityConfiguration = new SecurityConfiguration
 | 
			
		||||
            {
 | 
			
		||||
                AutoAcceptUntrustedCertificates = true,
 | 
			
		||||
                UseValidatedCertificates = true,
 | 
			
		||||
                AutoAcceptUntrustedCertificates = true,//自动接受证书
 | 
			
		||||
                RejectSHA1SignedCertificates = false,
 | 
			
		||||
                MinimumCertificateKeySize = 1024,
 | 
			
		||||
                SuppressNonceValidationErrors = true,
 | 
			
		||||
@@ -169,6 +156,8 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
            DisableHiResClock = true
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        certificateValidator.Update(m_configuration);
 | 
			
		||||
 | 
			
		||||
        m_configuration.Validate(ApplicationType.Client);
 | 
			
		||||
        m_application.ApplicationConfiguration = m_configuration;
 | 
			
		||||
 | 
			
		||||
@@ -195,6 +184,10 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string OPCUAName { get; set; } = "ThingsGateway";
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// SessionReconnectHandler
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public SessionReconnectHandler ReConnectHandler => m_reConnectHandler;
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 当前活动会话。
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -205,9 +198,9 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    #region 订阅
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 新增订阅,需要指定订阅的关键字,订阅的tag名数组,以及回调方法
 | 
			
		||||
    /// 新增订阅,需要指定订阅组名称,订阅的tag名数组
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void AddSubscription(string subscriptionName, string[] items)
 | 
			
		||||
    public async Task AddSubscriptionAsync(string subscriptionName, string[] items, bool loadType = true)
 | 
			
		||||
    {
 | 
			
		||||
        Subscription m_subscription = new(m_session.DefaultSubscription)
 | 
			
		||||
        {
 | 
			
		||||
@@ -220,18 +213,26 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
            DisplayName = subscriptionName
 | 
			
		||||
        };
 | 
			
		||||
        List<MonitoredItem> monitoredItems = new();
 | 
			
		||||
        var variableNodes = loadType ? await ReadNodesAsync(items) : null;
 | 
			
		||||
        for (int i = 0; i < items.Length; i++)
 | 
			
		||||
        {
 | 
			
		||||
            var item = new MonitoredItem
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                StartNodeId = new NodeId(items[i]),
 | 
			
		||||
                AttributeId = Attributes.Value,
 | 
			
		||||
                DisplayName = items[i],
 | 
			
		||||
                Filter = new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
 | 
			
		||||
                SamplingInterval = OPCNode?.UpdateRate ?? 1000,
 | 
			
		||||
            };
 | 
			
		||||
            item.Notification += Callback;
 | 
			
		||||
            monitoredItems.Add(item);
 | 
			
		||||
                var item = new MonitoredItem
 | 
			
		||||
                {
 | 
			
		||||
                    StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
 | 
			
		||||
                    AttributeId = Attributes.Value,
 | 
			
		||||
                    DisplayName = items[i],
 | 
			
		||||
                    Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
 | 
			
		||||
                    SamplingInterval = OPCNode?.UpdateRate ?? 1000,
 | 
			
		||||
                };
 | 
			
		||||
                item.Notification += Callback;
 | 
			
		||||
                monitoredItems.Add(item);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                _logAction?.Invoke(3, this, $"初始化{items[i]}变量订阅失败", ex);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        m_subscription.AddItems(monitoredItems);
 | 
			
		||||
 | 
			
		||||
@@ -239,14 +240,16 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
        m_subscription.Create();
 | 
			
		||||
        foreach (var item in m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)))
 | 
			
		||||
        {
 | 
			
		||||
            item.Filter = new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
 | 
			
		||||
            item.Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
 | 
			
		||||
        }
 | 
			
		||||
        m_subscription.ApplyChanges();
 | 
			
		||||
 | 
			
		||||
        var iserror = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
 | 
			
		||||
        if (iserror)
 | 
			
		||||
        var isError = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
 | 
			
		||||
        if (isError)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Error("创建以下变量订阅失败" + m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)).Select(a => a.StartNodeId.ToString() + ":" + a.Status.Error.ToString()).ToJson());
 | 
			
		||||
            _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)
 | 
			
		||||
@@ -256,7 +259,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
                // remove
 | 
			
		||||
                dic_subscriptions[subscriptionName].Delete(true);
 | 
			
		||||
                m_session.RemoveSubscription(dic_subscriptions[subscriptionName]);
 | 
			
		||||
                dic_subscriptions[subscriptionName].SafeDispose();
 | 
			
		||||
                try { dic_subscriptions[subscriptionName].Dispose(); } catch { }
 | 
			
		||||
                dic_subscriptions[subscriptionName] = m_subscription;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
@@ -277,7 +280,8 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
            {
 | 
			
		||||
                item.Value.Delete(true);
 | 
			
		||||
                m_session.RemoveSubscription(item.Value);
 | 
			
		||||
                item.Value.SafeDispose();
 | 
			
		||||
                try { item.Value.Dispose(); } catch { }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            dic_subscriptions.Clear();
 | 
			
		||||
        }
 | 
			
		||||
@@ -296,7 +300,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
                // remove
 | 
			
		||||
                dic_subscriptions[subscriptionName].Delete(true);
 | 
			
		||||
                m_session.RemoveSubscription(dic_subscriptions[subscriptionName]);
 | 
			
		||||
                dic_subscriptions[subscriptionName].SafeDispose();
 | 
			
		||||
                try { dic_subscriptions[subscriptionName].Dispose(); } catch { }
 | 
			
		||||
                dic_subscriptions.RemoveWhere(a => a.Key == subscriptionName);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -307,20 +311,30 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            VariableNode variableNode = (VariableNode)ReadNode(monitoreditem.StartNodeId.ToString(), false);
 | 
			
		||||
            var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
 | 
			
		||||
            foreach (var value in monitoreditem.DequeueValues())
 | 
			
		||||
            {
 | 
			
		||||
                var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
 | 
			
		||||
                if (data == null && value.Value != null)
 | 
			
		||||
                if (value.Value != null)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning($"{monitoreditem.StartNodeId}转换出错,原始值String为{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)
 | 
			
		||||
                    {
 | 
			
		||||
                        _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));
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var data = JValue.CreateNull();
 | 
			
		||||
                    DataChangedHandler?.Invoke((variableNode, value, data));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning($"{monitoreditem.StartNodeId}订阅事件出错:{ex.Message}");
 | 
			
		||||
            _logAction?.Invoke(3, this, $"{monitoreditem.StartNodeId}订阅处理错误", ex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
@@ -402,9 +416,9 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    /// <param name="end">结束时间</param>
 | 
			
		||||
    /// <param name="count">读取的个数</param>
 | 
			
		||||
    /// <param name="containBound">是否包含边界</param>
 | 
			
		||||
    /// <param name="token">token</param>
 | 
			
		||||
    /// <param name="cancellationToken">cancellationToken</param>
 | 
			
		||||
    /// <returns>读取的数据列表</returns>
 | 
			
		||||
    public async Task<List<DataValue>> ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false, CancellationToken token = default)
 | 
			
		||||
    public async Task<List<DataValue>> ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        HistoryReadValueId m_nodeToContinue = new()
 | 
			
		||||
        {
 | 
			
		||||
@@ -431,7 +445,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
             TimestampsToReturn.Both,
 | 
			
		||||
             false,
 | 
			
		||||
             nodesToRead,
 | 
			
		||||
             token);
 | 
			
		||||
             cancellationToken);
 | 
			
		||||
        var results = result.Results;
 | 
			
		||||
        var diagnosticInfos = result.DiagnosticInfos;
 | 
			
		||||
        ClientBase.ValidateResponse(results, nodesToRead);
 | 
			
		||||
@@ -450,12 +464,15 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #region 连接
 | 
			
		||||
    private ComplexTypeSystem typeSystem;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 连接到服务器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public async Task ConnectAsync()
 | 
			
		||||
    {
 | 
			
		||||
        await ConnectAsync(OPCNode.OPCUrl);
 | 
			
		||||
        _logAction?.Invoke(1, this, $"连接成功", null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -467,20 +484,70 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
        // disconnect any existing session.
 | 
			
		||||
        if (m_session != null)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Debug("断开连接");
 | 
			
		||||
            _logAction?.Invoke(1, this, $"主动断开连接", 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()
 | 
			
		||||
    {
 | 
			
		||||
        // stop any reconnect operation.
 | 
			
		||||
        if (m_reConnectHandler != null)
 | 
			
		||||
        {
 | 
			
		||||
            m_reConnectHandler.SafeDispose();
 | 
			
		||||
            try { m_reConnectHandler.Dispose(); } catch { }
 | 
			
		||||
            m_reConnectHandler = null;
 | 
			
		||||
        }
 | 
			
		||||
        m_session?.Close(10000);
 | 
			
		||||
        if (m_session != null)
 | 
			
		||||
        {
 | 
			
		||||
            m_session.KeepAlive -= Session_KeepAlive;
 | 
			
		||||
            m_session.Close(10000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
@@ -488,62 +555,74 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
 | 
			
		||||
    #region 读取/写入
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 从服务器读取值
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(string[] tags, CancellationToken token = default)
 | 
			
		||||
    public async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(string[] tags, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        var result = await ReadJTokenValueAsync(tags.Select(a => new NodeId(a)).ToArray(), token);
 | 
			
		||||
        var result = await ReadJTokenValueAsync(tags.Select(a => new NodeId(a)).ToArray(), cancellationToken);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 异步写opc标签
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public async Task<OperResult> WriteNodeAsync(string tag, JToken value, CancellationToken token = default)
 | 
			
		||||
    public async Task<Dictionary<string, Tuple<bool, string>>> WriteNodeAsync(Dictionary<string, JToken> writeInfoLists, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        Dictionary<string, Tuple<bool, string>> results = new();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            WriteValue valueToWrite = new()
 | 
			
		||||
            WriteValueCollection valuesToWrite = new();
 | 
			
		||||
            foreach (var item in writeInfoLists)
 | 
			
		||||
            {
 | 
			
		||||
                NodeId = new NodeId(tag),
 | 
			
		||||
                AttributeId = Attributes.Value,
 | 
			
		||||
            };
 | 
			
		||||
            var node = (VariableNode)ReadNode(tag.ToString(), false);
 | 
			
		||||
            //var expandedNodeId = NodeId.ToExpandedNodeId(node.DataType, m_session.NamespaceUris);
 | 
			
		||||
            //var type = m_session.Factory.GetSystemType(expandedNodeId);
 | 
			
		||||
            //var data = value.ToObject(type);
 | 
			
		||||
            //var dataValue = new DataValue(new Variant(data));
 | 
			
		||||
            var dataValue = JsonUtils.Decode(
 | 
			
		||||
                m_session.MessageContext,
 | 
			
		||||
                node.DataType,
 | 
			
		||||
                TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable),
 | 
			
		||||
                value.CalculateActualValueRank(),
 | 
			
		||||
                value
 | 
			
		||||
                );
 | 
			
		||||
            valueToWrite.Value = dataValue;
 | 
			
		||||
            WriteValueCollection valuesToWrite = new()
 | 
			
		||||
            {
 | 
			
		||||
                valueToWrite
 | 
			
		||||
            };
 | 
			
		||||
                WriteValue valueToWrite = new()
 | 
			
		||||
                {
 | 
			
		||||
                    NodeId = new NodeId(item.Key),
 | 
			
		||||
                    AttributeId = Attributes.Value,
 | 
			
		||||
                };
 | 
			
		||||
                var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken);
 | 
			
		||||
                var dataValue = JsonUtils.Decode(
 | 
			
		||||
                    m_session.MessageContext,
 | 
			
		||||
                    variableNode.DataType,
 | 
			
		||||
                    TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable),
 | 
			
		||||
                    item.Value.CalculateActualValueRank(),
 | 
			
		||||
                    item.Value
 | 
			
		||||
                    );
 | 
			
		||||
                valueToWrite.Value = dataValue;
 | 
			
		||||
 | 
			
		||||
                valuesToWrite.Add(valueToWrite);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var result = await m_session.WriteAsync(
 | 
			
		||||
     requestHeader: null,
 | 
			
		||||
     nodesToWrite: valuesToWrite, token);
 | 
			
		||||
     nodesToWrite: valuesToWrite, cancellationToken);
 | 
			
		||||
 | 
			
		||||
            ClientBase.ValidateResponse(result.Results, valuesToWrite);
 | 
			
		||||
            ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, valuesToWrite);
 | 
			
		||||
            if (!StatusCode.IsGood(result.Results[0]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var keys = writeInfoLists.Keys.ToList();
 | 
			
		||||
            for (int i = 0; i < keys.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                return new OperResult(result.Results[0].ToString());
 | 
			
		||||
                if (!StatusCode.IsGood(result.Results[i]))
 | 
			
		||||
                    results.Add(keys[i], Tuple.Create(true, result.Results[i].ToString()));
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    results.Add(keys[i], Tuple.Create(false, "成功"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return OperResult.CreateSuccessResult();
 | 
			
		||||
 | 
			
		||||
            return results;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult(ex);
 | 
			
		||||
            var keys = writeInfoLists.Keys.ToList();
 | 
			
		||||
            foreach (var item in keys)
 | 
			
		||||
            {
 | 
			
		||||
                results.Add(item, Tuple.Create(true, ex.Message));
 | 
			
		||||
            }
 | 
			
		||||
            return results;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
@@ -551,7 +630,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    /// <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)
 | 
			
		||||
        {
 | 
			
		||||
@@ -573,7 +652,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
             0,
 | 
			
		||||
             TimestampsToReturn.Neither,
 | 
			
		||||
             nodesToRead,
 | 
			
		||||
             token);
 | 
			
		||||
             cancellationToken);
 | 
			
		||||
        var results = result.Results;
 | 
			
		||||
        var diagnosticInfos = result.DiagnosticInfos;
 | 
			
		||||
        ClientBase.ValidateResponse(results, nodesToRead);
 | 
			
		||||
@@ -581,10 +660,10 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
        List<(string, DataValue, JToken)> jTokens = new();
 | 
			
		||||
        for (int i = 0; i < results.Count; i++)
 | 
			
		||||
        {
 | 
			
		||||
            var node = (VariableNode)ReadNode(nodeIds[i].ToString(), false);
 | 
			
		||||
            var type = TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable);
 | 
			
		||||
            var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken);
 | 
			
		||||
            var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable);
 | 
			
		||||
            var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
 | 
			
		||||
            jTokens.Add((node.NodeId.ToString(), results[i], jToken));
 | 
			
		||||
            jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
 | 
			
		||||
        }
 | 
			
		||||
        return jTokens.ToList();
 | 
			
		||||
    }
 | 
			
		||||
@@ -592,7 +671,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 从服务器或缓存读取节点
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private Node ReadNode(string nodeIdStr, bool isOnlyServer = true)
 | 
			
		||||
    private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isOnlyServer)
 | 
			
		||||
        {
 | 
			
		||||
@@ -601,21 +680,73 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
                return value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        NodeId nodeToRead = new(nodeIdStr);
 | 
			
		||||
        var node = m_session.ReadNode(nodeToRead);
 | 
			
		||||
        _variableDicts.AddOrUpdate(nodeIdStr, (VariableNode)node);
 | 
			
		||||
        var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
 | 
			
		||||
        if (OPCNode.LoadType)
 | 
			
		||||
            await typeSystem.LoadType(node.DataType);
 | 
			
		||||
        _variableDicts.AddOrUpdate(nodeIdStr, node);
 | 
			
		||||
        return node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #region 特性
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取一个节点的所有属性
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public async Task<OperResult<List<OPCNodeAttribute>>> ReadNoteAttributeAsync(string tag, uint attributesId, CancellationToken token = default)
 | 
			
		||||
    public async Task<List<OPCNodeAttribute>> ReadNoteAttributeAsync(string tag, uint attributesId, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        BrowseDescriptionCollection nodesToBrowse = new();
 | 
			
		||||
        ReadValueIdCollection nodesToRead = new();
 | 
			
		||||
@@ -638,16 +769,16 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
        };
 | 
			
		||||
        nodesToBrowse.Add(nodeToBrowse);
 | 
			
		||||
 | 
			
		||||
        var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, token);
 | 
			
		||||
        var result2 = result1.Copy<List<OPCNodeAttribute>>();
 | 
			
		||||
        result2.Content = result1.Content?.Values?.FirstOrDefault()?.ToList();
 | 
			
		||||
        var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        var result2 = result1.Values.FirstOrDefault();
 | 
			
		||||
        return result2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取节点的所有属性
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public async Task<OperResult<Dictionary<string, List<OPCNodeAttribute>>>> ReadNoteAttributeAsync(List<string> tags, CancellationToken token)
 | 
			
		||||
    public async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(List<string> tags, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        BrowseDescriptionCollection nodesToBrowse = new();
 | 
			
		||||
        ReadValueIdCollection nodesToRead = new();
 | 
			
		||||
@@ -677,7 +808,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, token);
 | 
			
		||||
        return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -843,10 +974,9 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    protected override void Dispose(bool disposing)
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        Disconnect();
 | 
			
		||||
        base.Dispose(disposing);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #region 私有方法
 | 
			
		||||
@@ -861,64 +991,16 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
            throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates a new session.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>The new session object.</returns>
 | 
			
		||||
    private async Task<ISession> ConnectAsync(string serverUrl)
 | 
			
		||||
    {
 | 
			
		||||
        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>());
 | 
			
		||||
        await new ComplexTypeSystem(m_session).Load(false, true).ConfigureAwait(false);
 | 
			
		||||
        Log.Debug("连接成功");
 | 
			
		||||
 | 
			
		||||
        m_session.KeepAliveInterval = OPCNode.KeepAliveInterval == 0 ? 60000 : OPCNode.KeepAliveInterval;
 | 
			
		||||
        m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
 | 
			
		||||
 | 
			
		||||
        //如果是订阅模式,连接时添加订阅组
 | 
			
		||||
        if (OPCNode.ActiveSubscribe)
 | 
			
		||||
            AddSubscription(Guid.NewGuid().ToString(), Variables.ToArray());
 | 
			
		||||
        return m_session;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private async Task<OperResult<Dictionary<string, List<OPCNodeAttribute>>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken token)
 | 
			
		||||
    private async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        int startOfProperties = nodesToRead.Count;
 | 
			
		||||
 | 
			
		||||
        ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(m_session, nodesToBrowse, false, token);
 | 
			
		||||
        ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(m_session, nodesToBrowse, false, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        if (references == null)
 | 
			
		||||
        {
 | 
			
		||||
            return new OperResult<Dictionary<string, List<OPCNodeAttribute>>>("浏览失败");
 | 
			
		||||
            throw new("浏览失败");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (int ii = 0; ii < references.Count; ii++)
 | 
			
		||||
@@ -940,7 +1022,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
            null,
 | 
			
		||||
            0,
 | 
			
		||||
            TimestampsToReturn.Neither,
 | 
			
		||||
            nodesToRead, token);
 | 
			
		||||
            nodesToRead, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        ClientBase.ValidateResponse(result.Results, nodesToRead);
 | 
			
		||||
        ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, nodesToRead);
 | 
			
		||||
@@ -996,8 +1078,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
                nodeAttributes.Add(nodeToRead.NodeId.ToString(), new() { item });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return OperResult.CreateSuccessResult(nodeAttributes);
 | 
			
		||||
        return nodeAttributes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -1011,17 +1092,15 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_session = m_reConnectHandler.Session;
 | 
			
		||||
        m_reConnectHandler.SafeDispose();
 | 
			
		||||
        m_reConnectHandler = null;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        if (checkLock.IsWaitting) { return; }
 | 
			
		||||
        checkLock.Wait();
 | 
			
		||||
        try
 | 
			
		||||
        lock (checkLock)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (!Object.ReferenceEquals(session, m_session))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
@@ -1029,7 +1108,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
 | 
			
		||||
            if (ServiceResult.IsBad(e.Status))
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning($"心跳检测错误:{e.Status}");
 | 
			
		||||
                _logAction?.Invoke(3, this, $"心跳检测错误:{e.Status}", null);
 | 
			
		||||
 | 
			
		||||
                if (m_reConnectHandler == null)
 | 
			
		||||
                {
 | 
			
		||||
@@ -1040,11 +1119,7 @@ public class OPCUAClient : DisposableObject
 | 
			
		||||
                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
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
 | 
			
		||||
 | 
			
		||||
internal static class CollectionExtensions
 | 
			
		||||
@@ -24,7 +20,7 @@ internal static class CollectionExtensions
 | 
			
		||||
    /// <typeparam name="T"></typeparam>
 | 
			
		||||
    /// <param name="this"></param>
 | 
			
		||||
    /// <param name="where"></param>
 | 
			
		||||
    public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
 | 
			
		||||
    internal static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (var obj in @this.Where(where).ToList())
 | 
			
		||||
        {
 | 
			
		||||
@@ -39,7 +35,7 @@ internal static class CollectionExtensions
 | 
			
		||||
    /// <param name="source"></param>
 | 
			
		||||
    /// <param name="selector"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static Task<TResult[]> SelectAsync<T, TResult>(this IEnumerable<T> source, Func<T, Task<TResult>> selector)
 | 
			
		||||
    internal static Task<TResult[]> SelectAsync<T, TResult>(this IEnumerable<T> source, Func<T, Task<TResult>> selector)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.WhenAll(source.Select(selector));
 | 
			
		||||
    }
 | 
			
		||||
@@ -0,0 +1,123 @@
 | 
			
		||||
#region copyright
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://diego2098.gitee.io/thingsgateway-docs/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
#endregion
 | 
			
		||||
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
 | 
			
		||||
//  CSDN博客:https://blog.csdn.net/qq_40374647
 | 
			
		||||
//  哔哩哔哩视频:https://space.bilibili.com/94253567
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/RRQM_Home
 | 
			
		||||
//  Github源代码仓库:https://github.com/RRQM
 | 
			
		||||
//  API首页:http://rrqm_home.gitee.io/touchsocket/
 | 
			
		||||
//  交流QQ群:234762506
 | 
			
		||||
//  感谢您的下载和使用
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// DictionaryExtension
 | 
			
		||||
/// </summary>
 | 
			
		||||
internal static class DictionaryExtension
 | 
			
		||||
{
 | 
			
		||||
    #region 字典扩展
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 移除满足条件的项目。
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="TKey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="pairs"></param>
 | 
			
		||||
    /// <param name="func"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static int RemoveWhen<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> pairs, Func<KeyValuePair<TKey, TValue>, bool> func)
 | 
			
		||||
    {
 | 
			
		||||
        var list = new List<TKey>();
 | 
			
		||||
        foreach (var item in pairs)
 | 
			
		||||
        {
 | 
			
		||||
            if (func?.Invoke(item) == true)
 | 
			
		||||
            {
 | 
			
		||||
                list.Add(item.Key);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var count = 0;
 | 
			
		||||
        foreach (var item in list)
 | 
			
		||||
        {
 | 
			
		||||
            if (pairs.TryRemove(item, out _))
 | 
			
		||||
            {
 | 
			
		||||
                count++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if NET45_OR_GREATER || NETSTANDARD2_0_OR_GREATER
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 尝试添加
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="Tkey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="dictionary"></param>
 | 
			
		||||
    /// <param name="tkey"></param>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static bool TryAdd<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
 | 
			
		||||
    {
 | 
			
		||||
        if (dictionary.ContainsKey(tkey))
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        dictionary.Add(tkey, value);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 尝试添加
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="Tkey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="dictionary"></param>
 | 
			
		||||
    /// <param name="tkey"></param>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static void AddOrUpdate<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey, TValue value)
 | 
			
		||||
    {
 | 
			
		||||
        if (dictionary.ContainsKey(tkey))
 | 
			
		||||
        {
 | 
			
		||||
            dictionary[tkey] = value;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            dictionary.Add(tkey, value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取值。如果键不存在,则返回默认值。
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="Tkey"></typeparam>
 | 
			
		||||
    /// <typeparam name="TValue"></typeparam>
 | 
			
		||||
    /// <param name="dictionary"></param>
 | 
			
		||||
    /// <param name="tkey"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    internal static TValue GetValue<Tkey, TValue>(this Dictionary<Tkey, TValue> dictionary, Tkey tkey)
 | 
			
		||||
    {
 | 
			
		||||
        return dictionary.TryGetValue(tkey, out var value) ? value : default;
 | 
			
		||||
    }
 | 
			
		||||
    #endregion 字典扩展
 | 
			
		||||
}
 | 
			
		||||
@@ -13,10 +13,7 @@
 | 
			
		||||
using Opc.Ua;
 | 
			
		||||
using Opc.Ua.Client;
 | 
			
		||||
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -150,16 +147,17 @@ public class FormUtils
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 浏览地址空间
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="session"></param>
 | 
			
		||||
    /// <param name="nodesToBrowse"></param>
 | 
			
		||||
    /// <param name="throwOnError"></param>
 | 
			
		||||
    /// <param name="token"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    /// <exception cref="ServiceResultException"></exception>
 | 
			
		||||
    public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError, CancellationToken token = default)
 | 
			
		||||
    public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -174,16 +172,23 @@ public class FormUtils
 | 
			
		||||
                        null,
 | 
			
		||||
                        null,
 | 
			
		||||
                        0,
 | 
			
		||||
                        nodesToBrowse, token);
 | 
			
		||||
                        nodesToBrowse, cancellationToken);
 | 
			
		||||
                var results = result.Results;
 | 
			
		||||
                var diagnosticInfos = result.DiagnosticInfos;
 | 
			
		||||
                ClientBase.ValidateResponse(results, nodesToBrowse);
 | 
			
		||||
                ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);
 | 
			
		||||
 | 
			
		||||
                ByteStringCollection continuationPoints = new();
 | 
			
		||||
                var continuationPoints = PrepareBrowseNext(result.Results);
 | 
			
		||||
 | 
			
		||||
                for (int ii = 0; ii < nodesToBrowse.Count; ii++)
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
                    // check if all references have been fetched.
 | 
			
		||||
                    if (results[ii].References.Count == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // check for error.
 | 
			
		||||
                    if (StatusCode.IsBad(results[ii].StatusCode))
 | 
			
		||||
                    {
 | 
			
		||||
@@ -198,33 +203,22 @@ public class FormUtils
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // check if all references have been fetched.
 | 
			
		||||
                    if (results[ii].References.Count == 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    // save results.
 | 
			
		||||
                    references.AddRange(results[ii].References);
 | 
			
		||||
 | 
			
		||||
                    // check for continuation point.
 | 
			
		||||
                    if (results[ii].ContinuationPoint != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        continuationPoints.Add(results[ii].ContinuationPoint);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // process continuation points.
 | 
			
		||||
                ByteStringCollection revisedContiuationPoints = new();
 | 
			
		||||
 | 
			
		||||
                while (continuationPoints.Count > 0)
 | 
			
		||||
                while (continuationPoints.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    // continue browse operation.
 | 
			
		||||
                    var nextResult = await session.BrowseNextAsync(
 | 
			
		||||
                          null,
 | 
			
		||||
                          true,
 | 
			
		||||
                          false,
 | 
			
		||||
                          continuationPoints
 | 
			
		||||
                          , token);
 | 
			
		||||
                          , cancellationToken);
 | 
			
		||||
                    results = nextResult.Results;
 | 
			
		||||
                    diagnosticInfos = nextResult.DiagnosticInfos;
 | 
			
		||||
                    ClientBase.ValidateResponse(results, continuationPoints);
 | 
			
		||||
@@ -232,6 +226,11 @@ public class FormUtils
 | 
			
		||||
 | 
			
		||||
                    for (int ii = 0; ii < continuationPoints.Count; ii++)
 | 
			
		||||
                    {
 | 
			
		||||
                        // check if all references have been fetched.
 | 
			
		||||
                        if (results[ii].References.Count == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // check for error.
 | 
			
		||||
                        if (StatusCode.IsBad(results[ii].StatusCode))
 | 
			
		||||
@@ -239,24 +238,16 @@ public class FormUtils
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // check if all references have been fetched.
 | 
			
		||||
                        if (results[ii].References.Count == 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        // save results.
 | 
			
		||||
                        references.AddRange(results[ii].References);
 | 
			
		||||
 | 
			
		||||
                        // check for continuation point.
 | 
			
		||||
                        if (results[ii].ContinuationPoint != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            revisedContiuationPoints.Add(results[ii].ContinuationPoint);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // check if browsing must continue;
 | 
			
		||||
                    revisedContiuationPoints = continuationPoints;
 | 
			
		||||
                    continuationPoints = PrepareBrowseNext(nextResult.Results);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // check if unprocessed results exist.
 | 
			
		||||
@@ -283,10 +274,10 @@ public class FormUtils
 | 
			
		||||
    /// <param name="session"></param>
 | 
			
		||||
    /// <param name="nodeToBrowse"></param>
 | 
			
		||||
    /// <param name="throwOnError"></param>
 | 
			
		||||
    /// <param name="token"></param>
 | 
			
		||||
    /// <param name="cancellationToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    /// <exception cref="ServiceResultException"></exception>
 | 
			
		||||
    public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescription nodeToBrowse, bool throwOnError, CancellationToken token = default)
 | 
			
		||||
    public static async Task<ReferenceDescriptionCollection> BrowseAsync(ISession session, BrowseDescription nodeToBrowse, bool throwOnError, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -305,7 +296,7 @@ public class FormUtils
 | 
			
		||||
                  null,
 | 
			
		||||
                  null,
 | 
			
		||||
                  0,
 | 
			
		||||
                  nodesToBrowse, token);
 | 
			
		||||
                  nodesToBrowse, cancellationToken);
 | 
			
		||||
            var results = result.Results;
 | 
			
		||||
            var diagnosticInfos = result.DiagnosticInfos;
 | 
			
		||||
            ClientBase.ValidateResponse(results, nodesToBrowse);
 | 
			
		||||
@@ -340,7 +331,7 @@ public class FormUtils
 | 
			
		||||
                var nextResult = await session.BrowseNextAsync(
 | 
			
		||||
                      null,
 | 
			
		||||
                      false,
 | 
			
		||||
                      continuationPoints, token);
 | 
			
		||||
                      continuationPoints, cancellationToken);
 | 
			
		||||
                results = nextResult.Results;
 | 
			
		||||
                diagnosticInfos = nextResult.DiagnosticInfos;
 | 
			
		||||
                ClientBase.ValidateResponse(results, continuationPoints);
 | 
			
		||||
@@ -813,7 +804,7 @@ public class FormUtils
 | 
			
		||||
    public static async Task<List<NodeId>> TranslateBrowsePaths(
 | 
			
		||||
        ISession session,
 | 
			
		||||
        NodeId startNodeId,
 | 
			
		||||
        NamespaceTable namespacesUris, CancellationToken token,
 | 
			
		||||
        NamespaceTable namespacesUris, CancellationToken cancellationToken,
 | 
			
		||||
        params string[] relativePaths)
 | 
			
		||||
    {
 | 
			
		||||
        // build the list of browse paths to follow by parsing the relative paths.
 | 
			
		||||
@@ -844,7 +835,7 @@ public class FormUtils
 | 
			
		||||
        var result = await session.TranslateBrowsePathsToNodeIdsAsync(
 | 
			
		||||
            null,
 | 
			
		||||
            browsePaths,
 | 
			
		||||
            token);
 | 
			
		||||
            cancellationToken);
 | 
			
		||||
        BrowsePathResultCollection results = result.Results;
 | 
			
		||||
        DiagnosticInfoCollection diagnosticInfos = result.DiagnosticInfos;
 | 
			
		||||
        // ensure that the server returned valid results.
 | 
			
		||||
@@ -1115,4 +1106,23 @@ public class FormUtils
 | 
			
		||||
            _ => valueRank.ToString(),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Create the continuation point collection from the browse result
 | 
			
		||||
    /// collection for the BrowseNext service.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="browseResultCollection">The browse result collection to use.</param>
 | 
			
		||||
    /// <returns>The collection of continuation points for the BrowseNext service.</returns>
 | 
			
		||||
    private static ByteStringCollection PrepareBrowseNext(BrowseResultCollection browseResultCollection)
 | 
			
		||||
    {
 | 
			
		||||
        var continuationPoints = new ByteStringCollection();
 | 
			
		||||
        foreach (var browseResult in browseResultCollection)
 | 
			
		||||
        {
 | 
			
		||||
            if (browseResult.ContinuationPoint != null)
 | 
			
		||||
            {
 | 
			
		||||
                continuationPoints.Add(browseResult.ContinuationPoint);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return continuationPoints;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -17,7 +17,7 @@ using Opc.Ua;
 | 
			
		||||
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Xml;
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Foundation.Adapter.OPCUA;
 | 
			
		||||
@@ -70,7 +70,7 @@ public static class JsonUtils
 | 
			
		||||
                        TypeId = new { Id = dataTypeId.Identifier, Namespace = dataTypeId.NamespaceIndex },
 | 
			
		||||
                        Body = json
 | 
			
		||||
                    }
 | 
			
		||||
                }.ToJson();
 | 
			
		||||
                }.ToJsonString();
 | 
			
		||||
                break;
 | 
			
		||||
            case BuiltInType.Variant:
 | 
			
		||||
                var type = TypeInfo.GetDataTypeId(GetSystemType(json.Type));
 | 
			
		||||
@@ -82,13 +82,13 @@ public static class JsonUtils
 | 
			
		||||
                        Body = json
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                }.ToJson();
 | 
			
		||||
                }.ToJsonString();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                newData = new
 | 
			
		||||
                {
 | 
			
		||||
                    Value = json
 | 
			
		||||
                }.ToJson();
 | 
			
		||||
                }.ToJsonString();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -109,7 +109,6 @@ public static class JsonUtils
 | 
			
		||||
        {
 | 
			
		||||
            if (ValueRank == ValueRanks.Scalar)
 | 
			
		||||
            {
 | 
			
		||||
                Type type = TypeInfo.GetSystemType(builtInType, ValueRank);
 | 
			
		||||
                switch (builtInType)
 | 
			
		||||
                {
 | 
			
		||||
                    case BuiltInType.Null: { var variant = decoder.ReadVariant(fieldName); return variant.Value; }
 | 
			
		||||
@@ -124,7 +123,7 @@ public static class JsonUtils
 | 
			
		||||
                    case BuiltInType.UInt64: { return decoder.ReadUInt64(fieldName); }
 | 
			
		||||
                    case BuiltInType.Float: { return decoder.ReadFloat(fieldName); }
 | 
			
		||||
                    case BuiltInType.Double: { return decoder.ReadDouble(fieldName); }
 | 
			
		||||
                    case BuiltInType.String: { return decoder.ReadString(fieldName); }
 | 
			
		||||
                    case BuiltInType.String: { return decoder.ReadField(fieldName, out var cancellationToken) ? cancellationToken?.ToString() : null; }
 | 
			
		||||
                    case BuiltInType.DateTime: { return decoder.ReadDateTime(fieldName); }
 | 
			
		||||
                    case BuiltInType.Guid: { return decoder.ReadGuid(fieldName); }
 | 
			
		||||
                    case BuiltInType.ByteString: { return decoder.ReadByteString(fieldName); }
 | 
			
		||||
@@ -138,7 +137,8 @@ public static class JsonUtils
 | 
			
		||||
                    case BuiltInType.DataValue: { return decoder.ReadDataValue(fieldName); }
 | 
			
		||||
                    case BuiltInType.Enumeration:
 | 
			
		||||
                        {
 | 
			
		||||
                            return type.IsEnum ? decoder.ReadEnumerated(fieldName, type) : (object)decoder.ReadInt32(fieldName);
 | 
			
		||||
                            Type type = TypeInfo.GetSystemType(builtInType, ValueRank);
 | 
			
		||||
                            return type.IsEnum ? decoder.ReadEnumerated(fieldName, type) : decoder.ReadInt32(fieldName);
 | 
			
		||||
                        }
 | 
			
		||||
                    case BuiltInType.DiagnosticInfo: { return decoder.ReadDiagnosticInfo(fieldName); }
 | 
			
		||||
                    case BuiltInType.Variant: { return decoder.ReadVariant(fieldName); }
 | 
			
		||||
@@ -163,7 +163,7 @@ public static class JsonUtils
 | 
			
		||||
    /// <param name="type"></param>
 | 
			
		||||
    /// <param name="value"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static JToken Encode(
 | 
			
		||||
    internal static JToken Encode(
 | 
			
		||||
        IServiceMessageContext Context,
 | 
			
		||||
        BuiltInType type,
 | 
			
		||||
        object value
 | 
			
		||||
@@ -172,7 +172,7 @@ public static class JsonUtils
 | 
			
		||||
        //对于Integer,Int64,Number等会转化为string JValue!
 | 
			
		||||
 | 
			
		||||
        using var encoder = CreateEncoder(Context, null, false);
 | 
			
		||||
        Encode(encoder, type, "Value", value, null);
 | 
			
		||||
        Encode(encoder, type, "Value", value);
 | 
			
		||||
        var textbuffer = encoder.CloseAndReturnText();
 | 
			
		||||
        using var stringReader = new StringReader(textbuffer);
 | 
			
		||||
        using var jsonReader = new JsonTextReader(stringReader);
 | 
			
		||||
@@ -180,7 +180,28 @@ public static class JsonUtils
 | 
			
		||||
        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 isCollection = (value is IList) && (builtInType != BuiltInType.ByteString);
 | 
			
		||||
@@ -300,7 +321,7 @@ public static class JsonUtils
 | 
			
		||||
                case BuiltInType.DataValue: { encoder.WriteDataValue(fieldName, (DataValue)value); return; }
 | 
			
		||||
                case BuiltInType.Enumeration:
 | 
			
		||||
                    {
 | 
			
		||||
                        if (value.GetType().IsEnum)
 | 
			
		||||
                        if (value?.GetType().IsEnum == true)
 | 
			
		||||
                        {
 | 
			
		||||
                            encoder.WriteEnumerated(fieldName, (Enum)value);
 | 
			
		||||
                        }
 | 
			
		||||
@@ -337,26 +358,6 @@ public static class JsonUtils
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
    #region json
 | 
			
		||||
@@ -366,7 +367,7 @@ public static class JsonUtils
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="jToken"></param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static int CalculateActualValueRank(this JToken jToken)
 | 
			
		||||
    internal static int CalculateActualValueRank(this JToken jToken)
 | 
			
		||||
    {
 | 
			
		||||
        if (jToken.Type != JTokenType.Array)
 | 
			
		||||
            return -1;
 | 
			
		||||
@@ -381,12 +382,6 @@ public static class JsonUtils
 | 
			
		||||
        }
 | 
			
		||||
        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)
 | 
			
		||||
    {
 | 
			
		||||
        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)
 | 
			
		||||
            .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)
 | 
			
		||||
    {
 | 
			
		||||
        return jsonType switch
 | 
			
		||||
@@ -421,4 +423,112 @@ public static class JsonUtils
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #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