Compare commits

...

182 Commits
7.0.0.23 ... v7

Author SHA1 Message Date
2248356998 qq.com
7590ba4ad5 更新插件 2025-01-06 20:57:36 +08:00
Diego
39ce18f8bb 更新readme 2025-01-06 16:06:01 +08:00
Diego
777a4f9d3f 更新插件 2025-01-06 14:29:51 +08:00
Diego
b91a938787 更新构建脚本 2025-01-06 14:02:41 +08:00
Diego
c903abdb1b 更新admin 2025-01-06 11:24:49 +08:00
Diego
3279bc0580 更新构建脚本 2025-01-06 11:12:04 +08:00
Diego
bd32c9f49e 更新插件 2025-01-06 10:28:14 +08:00
Diego
5507088e3d 更新插件 2025-01-06 09:48:55 +08:00
2248356998 qq.com
37ad48ae4a 7.2.3.6
更新依赖包
2025-01-05 23:46:31 +08:00
2248356998 qq.com
120c6e6d87 7.2.3.5
opcuaclient: 心跳机制异常
2025-01-05 00:46:52 +08:00
2248356998 qq.com
866e907b7e 同步 2025-01-02 20:47:47 +08:00
2248356998 qq.com
2bd8c2cb10 更新mc1e插件 2025-01-02 20:45:19 +08:00
Diego
c6c9919178 更新依赖包 2025-01-02 16:45:31 +08:00
Diego
575bf9d1e0 update Version MSBuildProperty 2025-01-02 13:56:38 +08:00
Diego
82a56e0285 更新构建脚本 2025-01-02 12:56:52 +08:00
Diego
25d5f7c132 设定动态编译缓存目录 2025-01-02 12:27:57 +08:00
Diego2098
a3e9ecf30f !56 恢复插件隔离
* 恢复插件域
2025-01-02 04:03:14 +00:00
2248356998 qq.com
ec5ff0a07f 7.2.1.3
更新mqttnet版本
更新admin
2025-01-01 16:25:19 +08:00
Diego
be836d30c5 更新admin 2024-12-30 12:11:08 +08:00
2248356998 qq.com
42b1529a5f 添加Release解决方案 2024-12-28 10:35:02 +08:00
2248356998 qq.com
47708c4807 添加Release解决方案 2024-12-28 10:30:17 +08:00
2248356998 qq.com
d2a51e004c 7.2.0.99
规则引擎:自定义脚本接口定义修改,增加Logger属性
数据库上传插件:回退修改,恢复string类型Value字段
2024-12-28 04:19:37 +08:00
2248356998 qq.com
ab14230101 修复通道报文日志失效 2024-12-26 21:36:09 +08:00
Diego
4bcf8c1f78 修复设备通讯变量点位 2024-12-26 15:38:19 +08:00
Diego
44d00e9da3 数据库插件实体类修改! 2024-12-26 14:45:25 +08:00
Diego
d7e6a4493c 更新依赖 2024-12-26 13:22:51 +08:00
Diego
53e89d8c54 7.2.0.95
修复 LL优先级比L低
添加报警属性验证规则
2024-12-25 12:26:06 +08:00
Diego
4497c13634 代码清理 2024-12-25 10:53:43 +08:00
Diego
4a35fade46 更新插件 2024-12-24 17:44:51 +08:00
Diego
6cba0601fd fix: 上一提交导致other通道创建错误 2024-12-24 17:21:10 +08:00
Diego
ed4332ea78 更新readme 2024-12-24 14:14:30 +08:00
Diego
aba069cec5 7.2.0.91
修复dlt645驱动
调整异步方法
2024-12-24 14:03:39 +08:00
Diego
8a82ac0a11 更新测试方法 2024-12-23 16:43:35 +08:00
Diego
1cd1456d75 7.2.0.87
更新admin
2024-12-23 15:31:16 +08:00
Diego
b791a3eb10 修复 动态插件属性 验证错误 2024-12-23 15:21:42 +08:00
2248356998 qq.com
3b22a8b170 7.2.0.85
更新依赖
2024-12-22 15:59:15 +08:00
2248356998 qq.com
419e8214ca 更新规则引擎,优化性能 2024-12-20 22:32:37 +08:00
Diego
b9f8571f0f 7.2.0.83
fix(VariableRuntime): 修改判断变量变化方法
feat(TestVariable): 修改添加测试变量UI
2024-12-20 14:28:44 +08:00
Diego
c6d9a9d7f8 修改判断变量变化方法 2024-12-20 14:27:29 +08:00
Diego
310aba6ccb 修改添加测试变量UI 2024-12-20 12:11:20 +08:00
Diego
9bd89ac4f6 添加 git 更新子模块 bat 2024-12-19 18:40:13 +08:00
Diego
ea6a51dca9 更新admin 2024-12-19 18:16:01 +08:00
Diego
e701bcc50c 调整插件基类,增加上传模式配置 2024-12-19 18:15:47 +08:00
Diego
a1abf06e75 7.2.0.78
更新opc插件
2024-12-19 16:11:13 +08:00
Diego
76ace394b0 更新opcua插件 2024-12-19 15:21:21 +08:00
Diego
4dac462f8f 更新解决方案 2024-12-19 12:21:32 +08:00
Diego
039672b1e7 更新admin 2024-12-19 12:19:55 +08:00
Diego
1b26ecbbf5 添加 admin 子模块 2024-12-19 11:54:14 +08:00
Diego2098
f4a7e96943 add Admin submodule.
Signed-off-by: Diego2098 <2248356998@qq.com>
2024-12-19 03:17:59 +00:00
Diego
a45bc2954f 移动文件夹 2024-12-19 11:16:24 +08:00
Diego
62f32467b7 移动文件夹 2024-12-19 11:15:18 +08:00
Diego
600a1bf201 7.2.0.75 2024-12-18 12:36:49 +08:00
Diego
7e196e7aa6 更新admin 2024-12-17 14:16:15 +08:00
Diego
9ad3507b66 7.2.0.72
修复变量导入错误
2024-12-17 13:51:51 +08:00
Diego
add1bdfcf6 更新admin 2024-12-17 13:26:27 +08:00
Diego
7ea8a7c079 7.2.0.70
修复初始值解析
2024-12-17 10:51:52 +08:00
Diego
28e31f5165 7.2.0.69
更新实体特性
2024-12-17 10:22:03 +08:00
Diego
10a6975c5d 7.2.0.68
更新modbusSlave插件,适配03功能码单写bit
2024-12-16 18:24:16 +08:00
Diego
b34ea87660 7.2.0.67
更新db插件,增加 单独配置实时表上传间隔时间 属性
2024-12-16 17:38:42 +08:00
Diego
86ed69c50b 7.2.0.66
更新版本
2024-12-16 11:29:52 +08:00
Diego
02e824154c 7.2.0.65
读取间隔增加cron支持
2024-12-16 11:13:23 +08:00
Matrix
dc973c8491 !55 TimeTick 新增 Cron
* TimeTick 新增 cron定时
2024-12-16 03:03:39 +00:00
2248356998 qq.com
0a28e3a8d3 7.2.0.64 2024-12-15 23:41:07 +08:00
2248356998 qq.com
7a48c260e1 更新插件 2024-12-14 15:22:19 +08:00
Diego
c55c49a3a2 添加TS自定义二进制序列化方式 2024-12-13 12:19:12 +08:00
Diego
0fc9b06d12 7.2.0.62
优化上传插件批量性能
2024-12-11 15:52:00 +08:00
Diego
d71ad5a6bf 单文件发布 2024-12-11 11:13:58 +08:00
Diego
f0b3028306 更新规则引擎 2024-12-11 09:15:27 +08:00
2248356998 qq.com
752992c527 7.2.0.60
更新admin
删除不需要的文件
2024-12-10 14:30:18 +08:00
Diego
43c4476396 7.2.0.58
更新aop框架版本
2024-12-09 15:43:29 +08:00
Diego
f8172bed56 修复单文件发布 2024-12-09 11:33:00 +08:00
Diego
c1f71b4cfc 7.2.0.57
添加数据库解密示例

更新依赖包
2024-12-09 11:11:16 +08:00
2248356998 qq.com
4799da15e7 7.2.0.53
更新语言资源
更新规则引擎
2024-12-08 22:01:16 +08:00
2248356998 qq.com
4936e47c7a 7.2.0.50 2024-12-07 18:34:51 +08:00
2248356998 qq.com
cb965373aa 更新插件 2024-12-07 18:30:25 +08:00
2248356998 qq.com
654a91184a 7.2.0.48
更新依赖
2024-12-07 17:51:48 +08:00
Diego
57997f7f4b 优化规则引擎 2024-12-06 10:45:56 +08:00
Diego
ca2f5be3d1 7.2.0.46
更新Admin,修复错误插件dll导致程序无法启动的问题
2024-12-05 17:15:54 +08:00
Diego
1b091073f1 7.2.0.45 2024-12-05 11:10:43 +08:00
Diego
85d2d3c442 更新pro插件 2024-12-05 11:09:31 +08:00
Diego
d77dfd63cf 增加规则引擎插件 2024-12-05 02:13:02 +08:00
Diego
db2bf52fca 调整脚本编辑UI 2024-12-04 08:48:27 +08:00
Diego
2c62d97440 迁移重启后台按钮到设备状态页面 2024-12-03 22:23:26 +08:00
Diego
53b2e64214 修复net6 界面白屏 2024-12-02 18:11:16 +08:00
Diego
ccf82a3ee5 添加layout显示条件 2024-12-02 17:08:50 +08:00
Diego
9cb6f35e60 修改代码规范文件,更新依赖包 2024-12-02 11:28:54 +08:00
2248356998 qq.com
da2e33a040 7.2.0.36 2024-11-30 21:00:05 +08:00
2248356998 qq.com
f2d260bfa4 动态调整线程池最大活动数目 2024-11-30 01:53:26 +08:00
Diego
783c7244f9 修正語言資源 2024-11-29 17:05:21 +08:00
Diego
c9287e0e63 更新依赖
命名空间变化
2024-11-29 16:53:00 +08:00
Diego
69b84d115c 添加变量导入校验缓存 2024-11-29 10:01:02 +08:00
Diego
1745b898b1 删除文件 2024-11-29 09:22:36 +08:00
Diego
5ba4c85249 更新依赖包,转移部分代码到插件仓库 2024-11-28 21:14:14 +08:00
Diego
03c87bb46d 更新admin 2024-11-28 14:13:50 +08:00
Diego
dff5a76e9e 更新docker构建脚本 2024-11-27 16:05:16 +08:00
Diego
50386f4ca4 cache try dispose 2024-11-26 11:29:52 +08:00
Diego
de6ba2ec80 更新版本 2024-11-25 13:26:11 +08:00
Diego
ce126015d9 更新pro插件 2024-11-25 12:52:04 +08:00
Diego
f2337c3d43 更新依赖 2024-11-25 11:38:09 +08:00
Diego
41b59d322a 更新依赖,添加bacnet,HJ212 2024-11-25 11:32:28 +08:00
2248356998 qq.com
dfea7c111e 更新依赖 2024-11-23 17:36:59 +08:00
2248356998 qq.com
3d5d672297 更新版本 2024-11-22 19:57:31 +08:00
2248356998 qq.com
e4dcc6f342 更新插件 2024-11-22 19:57:11 +08:00
Diego
9847249611 修改非数采插件的引用方式 2024-11-21 23:52:31 +08:00
Diego
19742a54ec 更新插件 2024-11-21 18:37:11 +08:00
Diego
5af9ae7a80 修复上个版本导致驱动sign量无法正常返回的bug 2024-11-21 17:42:11 +08:00
Diego
78962a4a75 去掉api日志显示 2024-11-21 17:05:13 +08:00
Diego
9a35d826aa 添加 更新服务 启用选项 2024-11-21 16:47:09 +08:00
Diego
c9cdc89d35 更新版本 2024-11-20 18:28:16 +08:00
Diego
33b75703ba 更新種子數據 2024-11-20 09:40:49 +08:00
Diego
ca55aa3a24 更新插件 2024-11-19 17:03:52 +08:00
Diego
95307c3902 更新版本文件 2024-11-19 12:10:40 +08:00
Diego
89c8eae7e2 更新依赖 2024-11-19 10:48:49 +08:00
Diego
91614772dc 更新版本 2024-11-19 10:28:42 +08:00
Diego
cd497ab4dc 更换快捷方式实现方式 2024-11-18 10:03:45 +08:00
2248356998 qq.com
f81895905e 更新依赖包 2024-11-18 00:24:18 +08:00
2248356998 qq.com
d82fafaa7a 更新后台版本 2024-11-17 14:14:28 +08:00
2248356998 qq.com
0ea01acc46 驱动在丢弃错误报文时提示日志 2024-11-16 20:58:46 +08:00
2248356998 qq.com
df55792a68 调试工具css更新 2024-11-16 20:29:36 +08:00
Diego
6a19c45269 更新版本 2024-11-15 21:51:39 +08:00
Diego
23ee345441 更新插件 2024-11-15 21:13:43 +08:00
Diego
0e8ff253f3 更新admin版本 2024-11-15 16:53:44 +08:00
2248356998 qq.com
89bddb0bc4 修复双机热备功能,添加日志显示 2024-11-14 22:34:03 +08:00
Diego
77bbca9bb3 调整公共页面位置 2024-11-14 15:13:34 +08:00
2248356998 qq.com
1acd4d8d58 更新版本 2024-11-14 00:31:33 +08:00
2248356998 qq.com
4db72a712d 更新依赖 2024-11-13 22:08:59 +08:00
Diego
da2b7ba08e 调整css 2024-11-13 17:32:39 +08:00
Diego
f8b49411bf 添加net9.0 2024-11-13 16:15:04 +08:00
Diego
a04b0d99b0 添加更新服务器 2024-11-12 16:14:09 +08:00
Diego
9e9735f617 更新admin版本 2024-11-11 17:28:34 +08:00
Diego
f906b36f01 更新插件版本 2024-11-11 17:26:12 +08:00
Diego
a9d1f4f854 更新版本 2024-11-10 20:29:42 +08:00
Diego
d017ae905f 添加重启软件功能 2024-11-10 01:18:05 +08:00
Diego
bede280507 种子数据默认不更新 2024-11-08 00:35:08 +08:00
Diego
ea130047bc 修复配置 2024-11-07 16:36:18 +08:00
Diego
d77d8de63e 更新依赖包 2024-11-07 16:27:44 +08:00
Diego
f5413d9202 修复插件关联设备数量缓存不刷新 2024-11-07 15:08:15 +08:00
Diego
559fc8f216 更新后台 2024-11-06 16:25:10 +08:00
Diego
34b2df911d 更新插件版本 2024-11-06 13:16:47 +08:00
Diego
3d5a8a8cbd Merge branch 'master' into release 2024-11-06 10:19:16 +08:00
Diego
2b07097e14 导入变量时写入机构id 2024-11-06 10:18:52 +08:00
Diego
372e943d8b Merge branch 'master' into release 2024-11-06 09:32:24 +08:00
Diego
95afcedc4f 更新后台 2024-11-06 09:32:01 +08:00
Diego
c1dc8f017c Merge branch 'master' into release 2024-11-06 09:15:04 +08:00
Diego
dbeddca263 整理格式 2024-11-06 09:13:09 +08:00
Diego
e65d133dec 更新依赖包 2024-11-06 09:02:11 +08:00
Diego
b5e1dea1c2 Merge branch 'master' into release 2024-11-05 14:52:58 +08:00
Diego
3f19469939 更新后台 2024-11-05 14:52:27 +08:00
Diego
87874b037a Merge branch 'master' into release 2024-11-05 11:59:50 +08:00
Diego
adda6841d1 更新后台版本 2024-11-05 11:58:24 +08:00
Diego
a8092b921b 添加机构 2024-11-05 06:05:23 +08:00
Diego
930e4d69d1 恢复sqlite数据库类型 2024-11-05 05:41:07 +08:00
Diego
d9219df45b 后台管理添加机构功能 2024-11-05 05:39:07 +08:00
Diego
a6d4ec28db 删除不必要的构建 2024-10-29 14:26:06 +08:00
Diego
b29e13f777 更新构建脚本 2024-10-28 17:08:35 +08:00
Diego
9363b78ff6 Merge branch 'master' of https://gitee.com/ThingsGateway/ThingsGateway 2024-10-28 16:58:42 +08:00
Diego
eba411fec7 更新插件 2024-10-28 16:49:09 +08:00
Diego2098
9afe99e134 !53 7.0.1.6
* Merge branch 'release' of gitee.com:ThingsGateway/ThingsGateway into master
* 更新依赖包
* 更新依赖包,雪花ID方法
* 更新依赖包,雪花ID方法
2024-10-28 00:40:55 +00:00
Diego2098
6678856178 Merge branch 'release' of gitee.com:ThingsGateway/ThingsGateway into master
Signed-off-by: Diego2098 <2248356998@qq.com>
2024-10-28 00:40:40 +00:00
Diego
a1fea38c2c 更新依赖包 2024-10-28 08:32:18 +08:00
Diego2098
2d3265e66d !52 7.0.1.5
* 更新依赖包,雪花ID方法
* 更新依赖包,雪花ID方法
2024-10-23 11:03:51 +00:00
Diego
f6d4400db9 更新依赖包,雪花ID方法 2024-10-23 19:01:13 +08:00
Diego
6d892a9266 更新依赖包,雪花ID方法 2024-10-23 19:00:23 +08:00
Diego2098
356fa08da5 !51 7.0.1.4
Merge pull request !51 from Diego2098/master
2024-10-16 15:13:04 +00:00
Diego
2225f8c890 修复数组类型比较错误 2024-10-16 22:37:35 +08:00
Diego2098
d1a89739f9 !50 7.0.1.3
Merge pull request !50 from Diego2098/master
2024-10-16 05:23:38 +00:00
Diego
6ec6896763 Merge branch 'master' of https://gitee.com/dotnetchina/ThingsGateway 2024-10-16 13:19:42 +08:00
Diego
9fb718d610 更新依赖包 2024-10-16 13:18:09 +08:00
Diego2098
c913b89a47 !49 7.0.1.2
Merge pull request !49 from Diego2098/master
2024-10-16 03:59:53 +00:00
Diego2098
58b45693b7 !48 7.0.1.2
Merge pull request !48 from Diego2098/release
2024-10-16 03:59:16 +00:00
Diego
29d3ece9b1 更新依赖包 2024-10-15 17:47:12 +08:00
Diego2098
7e5db75230 !47 7.0.1.2
Merge pull request !47 from Diego2098/master
2024-10-14 11:51:48 +00:00
Diego
283c0b9845 修改:更新脚本编辑UI 2024-10-14 19:51:22 +08:00
Diego2098
071a0f1651 !46 7.0.1.1
Merge pull request !46 from Diego2098/master
2024-10-14 11:07:55 +00:00
Diego
1a116557a4 修改:移入部分项目代码 2024-10-14 19:06:09 +08:00
Diego
1468297626 修改:更新依赖包 2024-10-12 23:41:40 +08:00
Diego
87e94fdf52 修改:更新依赖包 2024-10-12 10:59:12 +08:00
Diego
64e15c33c8 修改:更新依赖包 2024-10-12 10:57:10 +08:00
Diego2098
7170f435d1 !45 7.0.0.26
Merge pull request !45 from Diego2098/master
2024-10-11 07:31:21 +00:00
Diego
9942be0493 修复:更新依赖包,修复脚本编辑框无法显示的问题 2024-10-11 15:29:45 +08:00
Diego
eaad36277f 修改:脚本编辑更改代码编辑器 2024-10-10 17:25:37 +08:00
Diego
37cc498568 修改:脚本编辑更改代码编辑器 2024-10-10 16:44:39 +08:00
404 changed files with 18813 additions and 4478 deletions

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "Admin"]
url = https://gitee.com/ThingsGateway/BlazorAdmin
path = Admin

1
Admin Submodule

Submodule Admin added at 3b73b7283a

View File

@@ -10,6 +10,42 @@
[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)
## 源码获取/更新
### 源码克隆
注意因仓库包含子模块直接下载zip包会导致子模块丢失建议使用git clone命令
``` shell
https://gitee.com/ThingsGateway/ThingsGateway.git
```
### 源码更新
在vs中打开powerShell窗口执行以下命令或根目录下的`git_pull.bat`脚本
<img src="https://foruda.gitee.com/images/1736150639726525137/8ff84c29_7941935.png" width="400px" />
``` shell
chcp 65001
rem 更新主仓库
git pull
rem 初始化并更新所有子模块
git submodule update --init
pause
```
### 插件列表
#### 采集插件

9
git_pull.bat Normal file
View File

@@ -0,0 +1,9 @@
chcp 65001
rem 更新主仓库
git pull
rem 初始化并更新所有子模块
git submodule update --init
pause

View File

@@ -1,248 +1,162 @@
root = true
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
###############################
# Core EditorConfig Options #
###############################
###############################
# Core EditorConfig Options #
###############################
# All files
[*]
indent_style = space
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
spelling_exclusion_path = .\exclusion.dic
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True
dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field
dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef
dotnet_naming_rule.unity_serialized_field_rule.severity = warning
dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style
dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = *
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds =
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
dotnet_style_qualification_for_event = false:suggestion
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
# ReSharper properties
resharper_autodetect_indent_settings = true
resharper_formatter_off_tag = @formatter:off
resharper_formatter_on_tag = @formatter:on
resharper_formatter_tags_enabled = true
resharper_new_line_before_while = true
resharper_place_attribute_on_same_line = false
resharper_show_autodetect_configure_formatting_tip = false
resharper_use_indent_from_vs = false
# ReSharper inspection severities
resharper_arrange_redundant_parentheses_highlighting = hint
resharper_arrange_this_qualifier_highlighting = hint
resharper_arrange_type_member_modifiers_highlighting = hint
resharper_arrange_type_modifiers_highlighting = hint
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
resharper_built_in_type_reference_style_highlighting = hint
resharper_redundant_base_qualifier_highlighting = warning
resharper_suggest_var_or_type_built_in_types_highlighting = hint
resharper_suggest_var_or_type_elsewhere_highlighting = hint
resharper_suggest_var_or_type_simple_types_highlighting = hint
resharper_web_config_module_not_resolved_highlighting = warning
resharper_web_config_type_not_resolved_highlighting = warning
resharper_web_config_wrong_module_highlighting = warning
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
[*.{cs,css,js,json,*html,razor,txt,log}]
charset = utf-8-bom
[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
[*.json]
indent_size = 2
[*.{ps1,psm1}]
indent_size = 4
[*.sh]
indent_size = 4
end_of_line = lf
###############################
# .NET Coding Conventions #
###############################
[*.{cs,vb}]
# Organize usings
dotnet_sort_system_directives_first = false
# this. preferences
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
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
end_of_line = crlf
dotnet_style_prefer_collection_expression = when_types_exactly_match:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
###############################
# C# Coding Conventions #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
csharp_prefer_static_local_function = true:silent
# Expression-bodied members
#### 命名样式 ####
# 命名规则
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
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
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_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
###############################
# VB Coding Conventions #
###############################
csharp_style_namespace_declarations = block_scoped:silent
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
#### 命名样式 ####
# 命名规则
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]
# Add file header
file_header_template = ------------------------------------------------------------------------------\n此代码版权声明为全文件覆盖如有原作者特别声明会在下方手动补充\n此代码版权除特别声明外的代码归作者本人Diego所有\n源代码使用协议遵循本仓库的开源协议及附加协议\nGitee源代码仓库https://gitee.com/diego2098/ThingsGateway\nGithub源代码仓库https://github.com/kimdiego2098/ThingsGateway\n使用文档https://thingsgateway.cn/\nQQ群605534569\n------------------------------------------------------------------------------
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_expression_bodied_local_functions = true:silent
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_prefer_readonly_struct_member = true:suggestion
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
[*.{cs,vb}]
end_of_line = crlf
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
dotnet_diagnostic.CA2208.severity = none
dotnet_diagnostic.CA2008.severity = none
dotnet_diagnostic.CA1812.severity = none
dotnet_diagnostic.CA1508.severity = none
dotnet_diagnostic.CA1512.severity = none
dotnet_diagnostic.CA1513.severity = none
dotnet_diagnostic.CA1810.severity = none
dotnet_diagnostic.CA1814.severity = none
dotnet_diagnostic.CA1815.severity = none
dotnet_diagnostic.CA1835.severity = none
dotnet_diagnostic.CA1819.severity = none
dotnet_diagnostic.CA1823.severity = none
dotnet_diagnostic.CA2002.severity = none
dotnet_diagnostic.CA5350.severity = none
dotnet_diagnostic.CA5351.severity = none
dotnet_diagnostic.CA5358.severity = none
dotnet_diagnostic.CA5384.severity = none
dotnet_diagnostic.CA5392.severity = none
dotnet_diagnostic.CA1805.severity = none
dotnet_diagnostic.CA1851.severity = none
dotnet_diagnostic.CA1510.severity = none
dotnet_diagnostic.CA5401.severity = none
dotnet_diagnostic.CA2022.severity = none
dotnet_diagnostic.CA1848.severity = none
dotnet_diagnostic.CA2000.severity = none
dotnet_diagnostic.CA5394.severity = none
dotnet_diagnostic.CA3003.severity = none
dotnet_diagnostic.CA1515.severity = none
dotnet_diagnostic.CA1849.severity = none
dotnet_code_quality.CA1822.api_surface = private, internal

View File

@@ -1,7 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
</Project>

View File

@@ -1,40 +0,0 @@
{
"$schema": "null",
"AllowedHosts": "*",
"AppSettings": {
"InjectSpecificationDocument": true, // 生产环境是否开启Swagger
"ExternalAssemblies": [ "Plugins" ], // 插件目录
// nuget动态加载的程序集
"SupportPackageNamePrefixs": [
"ThingsGateway.Foundation.Razor",
"ThingsGateway.Debug.Razor",
"ThingsGateway.Core",
"ThingsGateway.Razor"
]
},
"DynamicApiControllerSettings": {
//"DefaultRoutePrefix": "api", // 默认路由前缀
"CamelCaseSeparator": "", // 驼峰命名分隔符
"SplitCamelCase": false, // 切割骆驼(驼峰)/帕斯卡命名
"LowercaseRoute": false, // 小写路由格式
"AsLowerCamelCase": true, // 小驼峰命名(首字母小写)
"KeepVerb": false, // 保留动作方法请求谓词
"KeepName": false // 保持原有名称不处理
},
"FriendlyExceptionSettings": {
"DefaultErrorMessage": "系统异常,请联系管理员",
"ThrowBah": true, // 是否将 Oops.Oh 默认抛出为业务异常
"LogError": false // 是否输出异常日志
},
"CorsAccessorSettings": {
"PolicyName": "ThingsGateway",
"WithExposedHeaders": [ "Content-Disposition", "X-Pagination", "access-token", "x-access-token" ], // 如果前端不代理且是axios请求
"SignalRSupport": true // 启用 SignalR 跨域支持
}
}

View File

@@ -1,33 +0,0 @@
{
//BootstrapBlazor配置
"BootstrapBlazorOptions": {
"ToastDelay": 4000,
"MessageDelay": 4000,
"SwalDelay": 4000,
"EnableErrorLogger": true,
"FallbackCulture": "zh-CN",
"SupportedCultures": [
"zh-CN",
"en-US",
"zh-TW"
],
"DefaultCultureInfo": "zh-CN", //修改默认语言
"TableSettings": {
"CheckboxColumnWidth": 36
},
"IgnoreLocalizerMissing": true,
"StepSettings": {
"Short": 1,
"Int": 1,
"Long": 1,
"Float": 0.1,
"Double": 0.01,
"Decimal": 0.01
}
}
}

View File

@@ -1,48 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"EventLog": {
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
} //windows事件输出日志等级
},
"Console": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
} //控制台输出日志等级
},
"BackendLog": {
"LogLevel": {
"Default": "Warning"
}
}
},
"Monitor": {
"GlobalEnabled": false, // 启用全局拦截日志
"IncludeOfMethods": [], // 拦截特定方法当GlobalEnabled=false有效
"ExcludeOfMethods": [], // 排除特定方法当GlobalEnabled=true有效
"BahLogLevel": "Information", // Oops.Oh 和 Oops.Bah 业务日志输出级别
"WithReturnValue": true, // 是否包含返回值默认true
"ReturnValueThreshold": 500, // 返回值字符串阈值默认0全量输出
"JsonBehavior": "None", // 是否输出Json默认None(OnlyJson、All)
"JsonIndented": false, // 是否格式化Json
"UseUtcTimestamp": false // 时间格式UTC、LOCAL
},
//日志配置
"LogJob": {
"DaysAgo": 10 //清理10天前日志
}
}

View File

@@ -1,40 +0,0 @@
{
"Menu": {
"MenuItems": [
{
"Url": "/",
"Text": "首页"
},
{
"Text": "Modbus",
"Items": [
{
"Url": "/ModbusMaster",
"Text": "ModbusMaster"
},
{
"Url": "/ModbusSlave",
"Text": "ModbusSlave"
}
]
},
{
"Url": "/SiemensS7Master",
"Text": "Siemens"
},
{
"Url": "/Dlt645_2007Master",
"Text": "Dlt645_2007Master"
},
{
"Url": "/OpcUaMaster",
"Text": "OpcUaMaster"
},
{
"Url": "/OpcDaMaster",
"Text": "OpcDaMaster"
}
]
}
}

View File

@@ -1,13 +0,0 @@
{
//网站配置
"Website": {
"Copyright": "版权所有 © 2023-present Diego",
"IsShowAbout": true, //是否显示关于页面
"SourceUrl": "https://gitee.com/diego2098/ThingsGateway",
"WikiUrl": "https://thingsgateway.cn/",
"QQGroup1Link": "http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569",
"QQGroup1Number": "605534569",
"Title": "ThingsGateway",
"Demo": false
}
}

View File

@@ -1,7 +0,0 @@
{
"ThingsGateway.Debug.MainLayout": {
"FullScreenButton": "Full Screen",
"About": "About"
}
}

View File

@@ -1,6 +0,0 @@
{
"ThingsGateway.Debug.MainLayout": {
"FullScreenButton": "全屏",
"About": "关于"
}
}

View File

@@ -1,6 +0,0 @@
{
"ThingsGateway.Debug.MainLayout": {
"FullScreenButton": "全屏",
"About": "關於"
}
}

View File

@@ -1,74 +0,0 @@
@inherits LayoutComponentBase
@layout BaseLayout
@namespace ThingsGateway.Debug
@using BootstrapBlazor.Components
@using ThingsGateway.Extension
@using ThingsGateway.NewLife.Extension
@using ThingsGateway.Razor
@inject NavigationManager NavigationManager
<div class="mainlayout">
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuService.MenuItems"
AllowDragTab=true AdditionalAssemblies="@_assemblyList"
UseTabSet="false" TabDefaultUrl="/">
<Header>
<div class="flex-fill"></div>
@* 搜索框 *@
<GlobalSearch Menus=@(MenuService.SameLevelMenuItems) />
@* 语言选择 *@
<div class="d-none d-xl-flex ">
<CultureChooser />
</div>
@* 全屏按钮 *@
<FullScreenButton class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-arrows-alt"
TooltipPlacement=Placement.Bottom TooltipText="@Localizer[nameof(FullScreenButton)]" />
@if (WebsiteOption.Value.IsShowAbout)
{
<Button OnClick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
@* 主题切换 *@
@* <ThemeToggle /> *@
<ThemeProvider class="layout-header-bar d-none d-lg-flex px-0"></ThemeProvider>
</Header>
<Side>
<div class="layout-banner">
<span class="avatar">
@WebsiteOption.Value.Title?.GetNameLen2()
</span>
<div class="layout-title d-flex align-items-center justify-content-center">
<span>@WebsiteOption.Value.Title</span>
</div>
</div>
</Side>
<Main>
<Tab ClickTabToNavigation="true" ShowExtendButtons="false" ShowClose="true" AllowDrag=true
Menus="@MenuService.MenuItems" AdditionalAssemblies="@_assemblyList"
DefaultUrl=@("/") Body=@(Body!) OnCloseTabItemAsync=@((a)=>
{
return Task.FromResult(!(a.Url=="/"||a.Url.IsNullOrEmpty()));
})>
</Tab>
</Main>
<NotAuthorized>
<Redirect />
</NotAuthorized>
</Layout>
</div>

View File

@@ -1,80 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ThingsGateway.Razor;
namespace ThingsGateway.Debug;
public partial class MainLayout
{
private List<Assembly> _assemblyList = new();
private string _versionString = string.Empty;
[Inject]
[NotNull]
private DialogService? DialogService { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<MainLayout>? Localizer { get; set; }
[Inject]
[NotNull]
private IMenuService? MenuService { get; set; }
[Inject]
[NotNull]
private IAppVersionService? VersionService { get; set; }
[Inject]
[NotNull]
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
protected override void OnInitialized()
{
_assemblyList = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a =>
a.GetTypes()).Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass
&& u.IsDefined(typeof(Microsoft.AspNetCore.Components.RouteAttribute), true)).Select(a => a.Assembly)
//.Where(a => a != typeof(BlazorApp).Assembly)
.Distinct().ToList();
base.OnInitialized();
}
protected override Task OnInitializedAsync()
{
_versionString = $"v{VersionService.Version}";
return base.OnInitializedAsync();
}
private async Task ShowAbout()
{
DialogOption? op = null;
op = new DialogOption()
{
IsScrolling = false,
Size = Size.Medium,
ShowFooter = false,
Title = Localizer["About"],
BodyTemplate = BootstrapDynamicComponent.CreateComponent<About>().Render(),
};
await DialogService.Show(op);
}
}

View File

@@ -1,138 +0,0 @@
::deep .avatar {
border-radius: 1.5rem;
width: 36px;
height: 36px;
background-color: var(--bs-green);
color: #fff;
flex: 0 0 auto;
font-size: 1rem;
}
.mainlayout ::deep .menu-icon {
width: 16px;
}
.mainlayout ::deep .layout-main > .tabs > .tabs-body {
background-color: var(--tabs-body-bg);
}
.mainlayout ::deep .layout-main > .tabs > .tabs-body > .tabs-body-content {
height: var(--bb-layout-body-height);
background-color: var(--bs-body-bg);
padding: 4px;
}
.mainlayout ::deep .tabs {
--bb-tabs-item-height: 32px;
--bb-tabs-body-padding: 0.5rem;
}
.mainlayout ::deep .tabs.tabs-border-card {
box-shadow: 0 0px 0px 0 rgba(0,0,0,0),0 0 6px 0 rgba(0,0,0,0);
}
.mainlayout ::deep .tabs .extend .nav-link-bar.left {
border-width: 0px 0px 0px 0px;
}
.mainlayout ::deep .tabs-nav-wrap > .nav-link-bar.dropdown {
border-width: 0px 0px 0px 0px;
}
.mainlayout ::deep .tabs .extend .nav-link-bar.right {
border-width: 0px 0px 0px 0px;
}
.mainlayout ::deep .tabs .tabs-item-fix {
border-width: 0px 0px 0px 0px;
}
.mainlayout ::deep .tabs.tabs-card > .tabs-header .tabs-item {
border-width: 0px 0px 0px 0px;
border: none;
}
.mainlayout ::deep .tabs.tabs-border-card > .tabs-header .tabs-item {
border-width: 0px 0px 0px 0px;
border: none;
}
.mainlayout ::deep .tabs.tabs-card .tabs-header .tabs-item.active {
border-width: 0px 0px 1px 0px;
border-style: solid;
border-color: var(--bb-tabs-item-active-color);
}
.mainlayout ::deep .tabs.tabs-border-card .tabs-header .tabs-item.active {
border-width: 0px 0px 1px 0px;
border-style: solid;
border-color: var(--bb-tabs-item-active-color);
}
.mainlayout ::deep .tabs.tabs-card .tabs-header .tabs-item.active {
background-color: var(--bs-primary-bg1);
}
.mainlayout ::deep.tabs.tabs-card .tabs-header .tabs-item:hover {
background-color: var(--bs-primary-bg1);
}
.mainlayout ::deep.tabs.tabs-border-card .tabs-header .tabs-item:hover {
background-color: var(--bs-primary-bg1);
}
.mainlayout ::deep .tabs-nav-wrap .nav-link-bar {
font-size: 0.7rem;
}
.mainlayout ::deep .tabs-item .tabs-item-close {
top: 5px;
}
.mainlayout ::deep .table-wrapper {
border-radius: unset;
}
.mainlayout ::deep .layout-side {
box-shadow: inset -1px 0 0px 0px var(--bs-border-color); /* 下内阴影 */
}
.mainlayout ::deep .layout-banner {
box-shadow: inset -1px 0 0px 0px var(--bs-border-color); /* 下内阴影 */
}
.mainlayout ::deep .layout {
--bb-layout-header-height: 44px;
--bb-layout-headerbar-background: transparent;
--bs-navbar-color: var(--bb-layout-header-color);
--bs-navbar-hover-color: var(--bs-primary);
--bb-layout-header-background: var(--tg-nav-bg);
--bb-layout-sidebar-background: var(--tg-nav-bg);
--bb-layout-footer-background: var(--tg-nav-bg);
--bb-layout-sidebar-banner-background: var(--tg-nav-bg);
--bb-layout-banner-font-size: 1.2rem;
--bb-layout-banner-logo-width: 36px;
--bb-layout-banner-logo-height: 36px;
--line-chart-height: 350px;
--bb-layout-body-height: calc(100vh - var(--bs-header-height) - var(--bb-layout-header-height) - 20px);
--line-chart-table-height: calc(100vh - var(--bs-header-height) - var(--bb-layout-header-height) - var(--line-chart-height) - 30px);
--table-height: calc(100vh - var(--bs-header-height) - var(--bb-layout-header-height) - 30px);
--bs-header-height: 30px;
}
.mainlayout ::deep .dropdown-logout {
--bb-logout-avatar-width: 32px;
--bb-logout-avatar-height: 32px;
--bb-logout-user-bg: rgba(52,58,64,0.7);
--bb-logout-menu-border-color: var(--bs-border-color);
}
.mainlayout ::deep .layout-header-bar {
border-color: transparent;
border: 0px;
color: var(--bb-layout-header-color);
}
.mainlayout ::deep .layout-header-bar:hover {
color: var(--bs-navbar-hover-color);
}

View File

@@ -1,96 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Photino.Blazor;
using System.Text;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Debug;
internal class Program
{
[STAThread]
private static void Main(string[] args)
{
//当前工作目录设为程序集的基目录
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
// 增加中文编码支持
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#region Logo
Console.Write(Environment.NewLine);
Console.ForegroundColor = ConsoleColor.Yellow;
XTrace.WriteLine(string.Empty);
Console.WriteLine(
"""
_______ _ _ _____ _
|__ __|| | (_) / ____| | |
| | | |__ _ _ __ __ _ ___ | | __ __ _ | |_ ___ __ __ __ _ _ _
| | | '_ \ | || '_ \ / _` |/ __|| | |_ | / _` || __|/ _ \\ \ /\ / // _` || | | |
| | | | | || || | | || (_| |\__ \| |__| || (_| || |_| __/ \ V V /| (_| || |_| |
|_| |_| |_||_||_| |_| \__, ||___/ \_____| \__,_| \__|\___| \_/\_/ \__,_| \__, |
__/ | __/ |
|___/ |___/
"""
);
Console.ResetColor();
#endregion Logo
var builder = PhotinoBlazorAppBuilder.CreateDefault(args);
builder.RootComponents.Add<Routes>("#app");
var options = GenericRunOptions.DefaultSilence
.ConfigureServices(services =>
{
foreach (var item in builder.Services)
{
services.Add(item);
}
});
;
Serve.BuildApplication(options, out var app);
app.Start();
var hybridApp = builder.Build(app.Services);
hybridApp.MainWindow.ContextMenuEnabled = false;
hybridApp.MainWindow.DevToolsEnabled = true;
hybridApp.MainWindow.GrantBrowserPermissions = true;
hybridApp.MainWindow.SetUseOsDefaultLocation(false);
hybridApp.MainWindow.SetUseOsDefaultSize(false);
hybridApp.MainWindow.SetSize(new System.Drawing.Size(1920, 1080));
hybridApp.MainWindow.SetTitle("ThingsGateway");
hybridApp.MainWindow.SetIconFile("wwwroot/favicon.ico");
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
{
};
hybridApp.MainWindow.WindowClosing += (sender, e) =>
{
app.StopAsync();
return false;
};
hybridApp.Run();
Thread.Sleep(5000);
}
}

View File

@@ -1,11 +0,0 @@
{
"profiles": {
"ThingsGateway.Debug.Photino": {
"commandName": "Project"
},
"WSL": {
"commandName": "WSL2",
"distributionName": ""
}
}
}

View File

@@ -1,48 +0,0 @@
@using Microsoft.AspNetCore.Components.Routing
@using ThingsGateway.Debug
@namespace ThingsGateway
@{
#if NET6_0
}
<Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(Razor.BaseLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
@{
#else
}
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(Razor.BaseLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@{
#endif
}

View File

@@ -1,65 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="$(SolutionDir)Version.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
<!--动态适用GC-->
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode>
<!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
</PropertyGroup>
<ItemGroup>
<Content Update="wwwroot\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="ThingsGateway.Razor" Version="$(AdminVersion)" />
<PackageReference Include="ThingsGateway.Debug.Razor" Version="$(PluginVersion)" />
<PackageReference Include="Photino.NET" Version="3.1.18" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="6.0.33" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.AspNetCore.Components.WebView" Version="8.0.10" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\ThingsGateway.Photino\Photino\**" LinkBase="Photino">
</Compile>
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,6 +0,0 @@
{
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹自动合并该文件夹里面所有json文件
"IgnoreConfigurationFiles": [ "" ],
"ExternalAssemblies": [ "" ]
}

View File

@@ -1,6 +0,0 @@
{
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹自动合并该文件夹里面所有json文件
"IgnoreConfigurationFiles": [ "" ],
"ExternalAssemblies": [ "" ]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,25 +0,0 @@
<!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.Debug</title>
<base href="/" />
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" rel="stylesheet">
<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
<link href="_content/BootstrapBlazor/css/motronic.min.css" rel="stylesheet">
<link href="ThingsGateway.Debug.Photino.styles.css" rel="stylesheet" />
<link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" />
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script>
</head>
<body>
<div id="app"></div>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src="_framework/blazor.webview.js" autostart="true"></script>
</body>
</html>

View File

@@ -1,51 +0,0 @@
using System.Drawing;
using System.Windows.Forms;
namespace ThingsGateway.Debug
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
var resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
SuspendLayout();
//
// MainForm
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1902, 1033);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(2);
Name = "MainForm";
Text = "ThingsGateway";
ResumeLayout(false);
}
#endregion
}
}

View File

@@ -1,116 +0,0 @@
// ------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
// ------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components.WebView;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Web.WebView2.Core;
using System.Windows.Forms;
namespace ThingsGateway.Debug
{
public partial class MainForm : Form
{
protected string UploadPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "uploads");
private BlazorWebView blazorWebView;
public MainForm(IServiceProvider serviceProvider)
{
InitializeComponent();
//默认全屏
//this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
//this.FormBorderStyle =FormBorderStyle.None;
//this.TopMost = true;
//this.KeyPreview = true;
KeyUp += new System.Windows.Forms.KeyEventHandler(MainForm_KeyUp);
blazorWebView = new BlazorWebView()
{
Dock = DockStyle.Fill,
HostPage = "wwwroot/index.html",
Services = serviceProvider
};
FormClosing += Program.Closing;
blazorWebView.RootComponents.Add<Routes>("#app");
Controls.Add(blazorWebView);
blazorWebView.BringToFront();
blazorWebView.KeyUp += new System.Windows.Forms.KeyEventHandler(MainForm_KeyUp);
blazorWebView.BlazorWebViewInitialized += BlazorWebViewInitialized;
blazorWebView.UrlLoading +=
(sender, urlLoadingEventArgs) =>
{
if (urlLoadingEventArgs.Url.Host != "0.0.0.0")
{
//外部链接WebView内打开,例如pdf浏览器
Console.WriteLine(urlLoadingEventArgs.Url);
urlLoadingEventArgs.UrlLoadingStrategy =
UrlLoadingStrategy.OpenInWebView;
}
};
}
private void BlazorWebViewInitialized(object? sender, EventArgs e)
{
//下载开始时引发 DownloadStarting阻止默认下载
blazorWebView.WebView.CoreWebView2.DownloadStarting += CoreWebView2_DownloadStarting;
//指定下载保存位置
blazorWebView.WebView.CoreWebView2.Profile.DefaultDownloadFolderPath = UploadPath;
////[无依赖发布webview2程序] 固定版本运行时环境的方式来实现加载网页
////设置web用户文件夹
//var browserExecutableFolder = "c:\\wb2";
//var userData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "BlazorWinFormsApp");
//Directory.CreateDirectory(userData);
//var creationProperties = new CoreWebView2CreationProperties()
//{
// UserDataFolder = userData,
// BrowserExecutableFolder = browserExecutableFolder
//};
//mainBlazorWebView.WebView.CreationProperties = creationProperties;
}
private void CoreWebView2_DownloadStarting(object? sender, CoreWebView2DownloadStartingEventArgs e)
{
var downloadOperation = e.DownloadOperation;
string fileName = Path.GetFileName(e.ResultFilePath);
var filePath = Path.Combine(UploadPath, fileName);
//指定下载保存位置
e.ResultFilePath = filePath;
MessageBox.Show($"下载文件完成 {fileName}", "提示");
}
private void MainForm_KeyUp(object? sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
if (WindowState == System.Windows.Forms.FormWindowState.Normal)
{
WindowState = System.Windows.Forms.FormWindowState.Maximized;
FormBorderStyle = FormBorderStyle.None;
}
else
{
WindowState = System.Windows.Forms.FormWindowState.Normal;
FormBorderStyle = FormBorderStyle.Sizable;
}
}
}
}
}

View File

@@ -1,197 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgvCjzYkwo82JsKPNifCjzYqwo82IcKPNgLCjzYAAAAAAAAA
AAAAAAAAAAAAAMKPNgDCjzYBwo82HsKPNknCjzZewo82QcKPNhLCjzYAwo82AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82S8KPNtPCjzbiwo8258KPNuDCjzatwo82EsKP
NgAAAAAAAAAAAAAAAADCjzYAwo82BsKPNmvCjzbbwo826MKPNtvCjzbhwo82vcKPNmDCjzYywo82AAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKPNgDCjzYPwo82O8KPNs7Cjzb/wo82iMKP
Nh7CjzYCwo82AAAAAAAAAAAAwo82AMKPNgDCjzZnwo8298KPNsLCjzY6wo82GsKPNjTCjzbJwo82/8KP
NpTCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKPNgDCjzYIwo82vsKP
Nv/CjzZowo82AAAAAAAAAAAAAAAAAAAAAADCjzYAwo82JMKPNtnCjzbxwo82QcKPNgDCjzYAwo82AMKP
NpHCjzb/wo82lsKPNgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwo82AMKP
NgjCjza9wo82/8KPNmjCjzYAAAAAAAAAAAAAAAAAwo82AMKPNgDCjzZ2wo82/8KPNrnCjzYKwo82AAAA
AADCjzYAwo82jcKPNv/CjzaWwo82AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADCjzYAwo82CMKPNr3Cjzb/wo82aMKPNgAAAAAAAAAAAAAAAADCjzYAwo82CcKPNrrCjzb/wo82fcKP
NgDCjzYAAAAAAMKPNgDCjzaLwo82/8KPNpbCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAMKPNgDCjzYIwo82vMKPNv/CjzZpwo82AAAAAAAAAAAAAAAAAMKPNgDCjzYfwo8238KP
Nv3CjzZRwo82AMKPNgDCjzYBwo82B8KPNpnCjzb/wo82psKPNgrCjzYAwo82AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgjCjza8wo82/8KPNmnCjzYAAAAAAAAAAAAAAAAAwo82AMKP
NjbCjzbxwo829sKPNj7CjzYAwo82AMKPNiPCjzaywo827cKPNv/Cjzbywo82qMKPNhPCjzYAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82CMKPNrvCjzb/wo82acKPNgAAAAAAAAAAAAAA
AADCjzYAwo82P8KPNvfCjzbzwo82OcKPNgDCjzYAwo82D8KPNlnCjzZiwo82X8KPNmDCjzZRwo82CcKP
NgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKPNgDCjzYHwo82usKPNv/CjzZpwo82AAAA
AAAAAAAAAAAAAMKPNgDCjzY+wo829sKPNvTCjzY8wo82AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgfCjza6wo82/8KP
NmnCjzYAAAAAAAAAAAAAAAAAwo82AMKPNirCjzbpwo82+MKPNkDCjzYAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82B8KP
NrnCjzb/wo82acKPNgAAAAAAAAAAAAAAAADCjzYAwo82FcKPNtLCjzb/wo82VsKPNgAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNgLCjzYTwo82BMKP
NgDCjzYHwo82t8KPNv/CjzZpwo82AMKPNgDCjzYMwo82E8KPNgDCjzYCwo82n8KPNv/CjzaEwo82AMKP
NgAAAAAAwo82AMKPNgDCjzZPwo82XcKPNgLCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82F8KP
NrTCjzY6wo82AMKPNgbCjza0wo82/8KPNmnCjzYAwo82AMKPNnPCjzaawo82A8KPNgDCjzZOwo82+MKP
NsbCjzYRwo82AAAAAADCjzYAwo82DcKPNsLCjzagwo82AMKPNgAAAAAAAAAAAAAAAAAAAAAAAAAAAMKP
NgDCjzYPwo82ysKPNpPCjzYBwo82BcKPNrHCjzb/wo82acKPNgDCjzYUwo82zMKPNpPCjzYAwo82AMKP
NgrCjzanwo82/MKPNmnCjzYAwo82AMKPNgDCjzZRwo8298KPNnbCjzYAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAwo82AMKPNgDCjzaawo827cKPNmHCjzYswo82vMKPNv/CjzaEwo82K8KPNoDCjzb5wo82ZMKP
NgAAAAAAwo82AMKPNi7CjzbWwo825sKPNlnCjzYdwo82SMKPNtTCjzb9wo82UsKPNgAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAwo82AMKPNmHCjzb3wo828cKPNt/Cjzbuwo8298KPNurCjzbjwo829MKP
NurCjzY6wo82AAAAAADCjzYAwo82AMKPNjfCjza8wo826cKPNtvCjzbewo82ycKPNs7CjzYvwo82AAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCjzYAwo82EsKPNj3CjzZAwo82QcKPNkDCjzY/wo82QMKP
NkDCjzY/wo82OMKPNgrCjzYAAAAAAAAAAADCjzYAwo82AMKPNg/CjzY5wo82SsKPNjDCjzYOwo82G8KP
NgXCjzYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////wH4H/8B8Af/AfAH/4f
hx/+H4cf/h8PH/4fDAf+HwwH/h8MB/4fD//+Hw///h8P/+IZD4/iGIcP4BGHH+ABwB/wAeAf8AHwH///
//////////////////////////////////8=
</value>
</data>
</root>

View File

@@ -1,87 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Text;
using System.Windows.Forms;
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.Debug;
internal class Program
{
internal static void Closing(object? sender, FormClosingEventArgs e)
{
host.StopAsync();
}
[STAThread]
private static void Main(string[] args)
{
//当前工作目录设为程序集的基目录
System.IO.Directory.SetCurrentDirectory(AppContext.BaseDirectory);
// 增加中文编码支持
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#region Logo
Console.Write(Environment.NewLine);
Console.ForegroundColor = ConsoleColor.Yellow;
XTrace.WriteLine(string.Empty);
Console.WriteLine(
"""
_______ _ _ _____ _
|__ __|| | (_) / ____| | |
| | | |__ _ _ __ __ _ ___ | | __ __ _ | |_ ___ __ __ __ _ _ _
| | | '_ \ | || '_ \ / _` |/ __|| | |_ | / _` || __|/ _ \\ \ /\ / // _` || | | |
| | | | | || || | | || (_| |\__ \| |__| || (_| || |_| __/ \ V V /| (_| || |_| |
|_| |_| |_||_||_| |_| \__, ||___/ \_____| \__,_| \__|\___| \_/\_/ \__,_| \__, |
__/ | __/ |
|___/ |___/
"""
);
Console.ResetColor();
#endregion Logo
var options = GenericRunOptions.DefaultSilence
.ConfigureServices(services =>
{
services.AddWindowsFormsBlazorWebView();
});
;
Serve.BuildApplication(options, out var app);
host = app;
app.Start();
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
{
MessageBox.Show(text: error.ExceptionObject.ToString(), caption: "Error");
};
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm(app.Services));
Thread.Sleep(5000);
}
private static IHost host;
}

View File

@@ -1,48 +0,0 @@
@using Microsoft.AspNetCore.Components.Routing
@using ThingsGateway.Debug
@namespace ThingsGateway
@{
#if NET6_0
}
<Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(Razor.BaseLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState>
@{
#else
}
<Router AppAssembly="@typeof(Routes).Assembly" AdditionalAssemblies="App.RazorAssemblies.Where(a=>a!=typeof(Routes).Assembly)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(Razor.BaseLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@{
#endif
}

View File

@@ -1,61 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="$(SolutionDir)Version.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<TargetFrameworks>net8.0-windows;</TargetFrameworks>
<!--动态适用GC-->
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode>
<!--使用自托管线程池-->
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
<!--使用工作站GC-->
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>-->
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="ThingsGateway.Razor" Version="$(AdminVersion)" />
<PackageReference Include="ThingsGateway.Debug.Razor" Version="$(PluginVersion)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="8.0.91" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="..\ThingsGateway.Debug.Photino\Locales\en-US.json" Link="Locales\en-US.json" />
<EmbeddedResource Include="..\ThingsGateway.Debug.Photino\Locales\zh-CN.json" Link="Locales\zh-CN.json" />
<EmbeddedResource Include="..\ThingsGateway.Debug.Photino\Locales\zh-TW.json">
<Link>Locales\zh-TW.json</Link>
</EmbeddedResource>
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="..\ThingsGateway.Debug.Photino\MainLayout.razor" Link="MainLayout.razor" />
<Compile Include="..\ThingsGateway.Debug.Photino\MainLayout.razor.cs" Link="MainLayout.razor.cs" />
<Content Include="..\ThingsGateway.Debug.Photino\MainLayout.razor.css" Link="MainLayout.razor.css" />
<Content Include="..\ThingsGateway.Debug.Photino\Configuration\*" LinkBase="Configuration">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,6 +0,0 @@
{
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹自动合并该文件夹里面所有json文件
"IgnoreConfigurationFiles": [ "" ],
"ExternalAssemblies": [ "" ]
}

View File

@@ -1,6 +0,0 @@
{
"ConfigurationScanDirectories": [ "Configuration", "" ], // 扫描配置文件json文件夹自动合并该文件夹里面所有json文件
"IgnoreConfigurationFiles": [ "" ],
"ExternalAssemblies": [ "" ]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,25 +0,0 @@
<!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.Debug</title>
<base href="/" />
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" rel="stylesheet">
<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet">
<link href="_content/BootstrapBlazor/css/motronic.min.css" rel="stylesheet">
<link href="ThingsGateway.Debug.Winform.styles.css" rel="stylesheet" />
<link href="/_content/ThingsGateway.Razor/css/site.css" rel="stylesheet" />
<script src="/_content/ThingsGateway.Razor/js/theme.js" type="module"></script><!-- 初始主题 -->
<script src="/_content/ThingsGateway.Razor/js/culture.js"></script>
</head>
<body>
<div id="app"></div>
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js"></script>
<script src="_framework/blazor.webview.js" autostart="true"></script>
</body>
</html>

View File

@@ -1,14 +1,29 @@
<Project>
<PropertyGroup>
<AdminVersion>7.0.0.63</AdminVersion>
<PluginVersion>9.0.0.13</PluginVersion>
<ProPluginVersion>9.0.0.16</ProPluginVersion>
</PropertyGroup>
<PropertyGroup>
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;</NoWarn>
<TargetFrameworks>net8.0;net6.0;</TargetFrameworks>
<AdminVersion>7.2.0.64</AdminVersion>
<GatewayVersion>7.2.3.9</GatewayVersion>
<PluginVersion>9.0.3.10</PluginVersion>
<ProPluginVersion>9.0.3.6</ProPluginVersion>
</PropertyGroup>
<PropertyGroup>
<AnalysisModeDesign>None</AnalysisModeDesign>
<AnalysisModeDocumentation>All</AnalysisModeDocumentation>
<AnalysisModeGlobalization>None</AnalysisModeGlobalization>
<AnalysisModeInteroperability>All</AnalysisModeInteroperability>
<AnalysisModeMaintainability>All</AnalysisModeMaintainability>
<AnalysisModeNaming>None</AnalysisModeNaming>
<AnalysisModePerformance>All</AnalysisModePerformance>
<AnalysisModeSingleFile>All</AnalysisModeSingleFile>
<AnalysisModeReliability>All</AnalysisModeReliability>
<AnalysisModeSecurity>All</AnalysisModeSecurity>
<AnalysisModeUsage>None</AnalysisModeUsage>
<AnalysisModeStyle>None</AnalysisModeStyle>
<NoWarn>CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;</NoWarn>
<TargetFrameworks>net9.0;net8.0;net6.0;</TargetFrameworks>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -24,7 +39,7 @@
<ItemGroup>
<None Include="$(SolutionDir)Directory.Build.props" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup>
<DebugSymbols>True</DebugSymbols>
<DebugType>Embedded</DebugType>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net462;netstandard2.0;net8.0;net6.0;</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;net9.0;net8.0;net6.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Content Remove="Locales\*.json" />

View File

@@ -0,0 +1,145 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using CSScripting;
using CSScriptLib;
using System.Reflection;
using System.Text;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Threading;
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 脚本扩展方法
/// </summary>
public static class CSharpScriptEngineExtension
{
private static string CacheKey = $"{nameof(CSharpScriptEngineExtension)}-{nameof(Do)}";
private static object m_waiterLock = new object();
/// <summary>清理计时器</summary>
private static TimerX? _clearTimer;
static CSharpScriptEngineExtension()
{
if (_clearTimer == null)
{
_clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true };
}
}
private static void RemoveNotAlive(Object? state)
{
//检测缓存
try
{
var data = Instance.GetAll();
lock (m_waiterLock)
{
foreach (var item in data)
{
if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000)
{
Instance.Remove(item.Key);
item.Value?.Value?.TryDispose();
item.Value?.Value?.GetType().Assembly.Unload();
GC.Collect();
}
}
}
}
catch
{
}
}
private static MemoryCache Instance { get; set; } = new MemoryCache();
/// <summary>
/// 执行脚本获取返回值
/// </summary>
public static T Do<T>(string source, params Assembly[] assemblies) where T : class
{
var field = $"{CacheKey}-{source}";
var runScript = Instance.Get<T>(field);
if (runScript == null)
{
lock (m_waiterLock)
{
runScript = Instance.Get<T>(field);
if (runScript == null)
{
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var _using = new StringBuilder();
var _body = new StringBuilder();
src.ToList().ForEach(l =>
{
if (l.StartsWith("using "))
{
_using.AppendLine(l);
}
else
{
_body.AppendLine(l);
}
});
var evaluator = CSScript.Evaluator;
foreach (var item in assemblies)
{
evaluator = evaluator.ReferenceAssembly(item.Location);
}
// 动态加载并执行代码
runScript = evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<T>(
$@"
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.Gateway.Application.Extensions;
{_using}
{_body}
");
GC.Collect();
Instance.Set(field, runScript);
}
}
}
Instance.SetExpire(field, TimeSpan.FromHours(1));
return runScript;
}
public static void SetExpire(string source, TimeSpan? timeSpan = null)
{
var field = $"{CacheKey}-{source}";
Instance.SetExpire(field, timeSpan ?? TimeSpan.FromHours(1));
}
}

View File

@@ -0,0 +1,198 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using CSScripting;
using CSScriptLib;
using System.Text;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Caching;
using ThingsGateway.NewLife.Threading;
using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application.Extensions;
/// <summary>
/// 读写表达式脚本
/// </summary>
public interface ReadWriteExpressions
{
public TouchSocket.Core.ILog? Logger { get; set; }
/// <summary>
/// 获取新值
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
object GetNewValue(object a);
}
/// <summary>
/// 表达式扩展
/// </summary>
public static class ExpressionEvaluatorExtension
{
private static string CacheKey = $"{nameof(ExpressionEvaluatorExtension)}-{nameof(GetReadWriteExpressions)}";
private static object m_waiterLock = new object();
/// <summary>清理计时器</summary>
private static TimerX? _clearTimer;
static ExpressionEvaluatorExtension()
{
if (_clearTimer == null)
{
_clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true };
}
}
private static void RemoveNotAlive(Object? state)
{
//检测缓存
try
{
var data = Instance.GetAll();
lock (m_waiterLock)
{
foreach (var item in data)
{
if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000)
{
Instance.Remove(item.Key);
item.Value?.Value?.TryDispose();
item.Value?.Value?.GetType().Assembly.Unload();
GC.Collect();
}
}
}
}
catch
{
}
}
private static MemoryCache Instance { get; set; } = new MemoryCache();
/// <summary>
/// 添加或获取脚本,非线程安全
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static ReadWriteExpressions GetOrAddScript(string source)
{
var field = $"{CacheKey}-{source}";
var runScript = Instance.Get<ReadWriteExpressions>(field);
if (runScript == null)
{
if (!source.Contains("return"))
{
source = $"return {source}";//只判断简单脚本中可省略return字符串
}
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var _using = new StringBuilder();
var _body = new StringBuilder();
src.ToList().ForEach(l =>
{
if (l.StartsWith("using "))
{
_using.AppendLine(l);
}
else
{
_body.AppendLine(l);
}
});
// 动态加载并执行代码
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>(
$@"
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.Gateway.Application.Extensions;
{_using}
public class Script:ReadWriteExpressions
{{
public TouchSocket.Core.ILog? Logger {{ get; set; }}
public object GetNewValue(object raw)
{{
{_body};
}}
}}
");
GC.Collect();
Instance.Set(field, runScript);
}
return runScript;
}
/// <summary>
/// 计算表达式:例如:(int)raw*100raw为原始值
/// </summary>
public static object GetExpressionsResult(this string expressions, object rawvalue)
{
if (string.IsNullOrWhiteSpace(expressions))
{
return rawvalue;
}
var readWriteExpressions = GetReadWriteExpressions(expressions);
var value = readWriteExpressions.GetNewValue(rawvalue);
return value;
}
/// <summary>
/// 计算表达式:例如:(int)raw*100raw为原始值
/// </summary>
public static object GetExpressionsResult(this string expressions, object rawvalue, ILog logger)
{
if (string.IsNullOrWhiteSpace(expressions))
{
return rawvalue;
}
var readWriteExpressions = GetReadWriteExpressions(expressions);
readWriteExpressions.Logger = logger;
var value = readWriteExpressions.GetNewValue(rawvalue);
return value;
}
/// <summary>
/// 执行脚本获取返回值ReadWriteExpressions
/// </summary>
public static ReadWriteExpressions GetReadWriteExpressions(string source)
{
var field = $"{CacheKey}-{source}";
var runScript = Instance.Get<ReadWriteExpressions>(field);
if (runScript == null)
{
lock (m_waiterLock)
{
runScript = GetOrAddScript(source);
}
}
Instance.SetExpire(field, TimeSpan.FromHours(1));
return runScript;
}
public static void SetExpire(string source, TimeSpan? timeSpan = null)
{
var field = $"{CacheKey}-{source}";
Instance.SetExpire(field, timeSpan ?? TimeSpan.FromHours(1));
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)PackNuget.props" />
<Import Project="$(SolutionDir)FoundationVersion.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net9.0;net8.0;net6.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="CS-Script" Version="4.8.21" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net9.0'">
<PackageReference Include="CS-Script" Version="4.8.19" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,115 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Debug;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Core.Json.Extension;
using ThingsGateway.Foundation;
using TouchSocket.Core;
/// <summary>
/// 调试UI
/// </summary>
public abstract class AdapterDebugBase : ComponentBase, IDisposable
{
/// <inheritdoc/>
~AdapterDebugBase()
{
this.SafeDispose();
}
/// <summary>
/// 长度
/// </summary>
public int ArrayLength { get; set; } = 1;
/// <summary>
/// 默认读写设备
/// </summary>
[Parameter]
public IProtocol Plc { get; set; }
/// <summary>
/// 变量地址
/// </summary>
public string RegisterAddress { get; set; } = "400001";
/// <summary>
/// 写入值
/// </summary>
public string WriteValue { get; set; }
/// <summary>
/// 数据类型
/// </summary>
protected DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
[Inject]
private IStringLocalizer<AdapterDebugBase> Localizer { get; set; }
/// <inheritdoc/>
public void Dispose()
{
Plc?.SafeDispose();
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public virtual async Task ReadAsync()
{
if (Plc != null)
{
try
{
var data = await Plc.ReadAsync(RegisterAddress, ArrayLength, DataType);
if (data.IsSuccess)
{
Plc.Logger?.LogInformation(data.Content.ToJsonNetString());
}
else
{
Plc.Logger?.Warning(data.ToString());
}
}
catch (Exception ex)
{
Plc.Logger?.Exception(ex);
}
}
}
/// <inheritdoc/>
public virtual async Task WriteAsync()
{
if (Plc != null)
{
try
{
var data = await Plc.WriteAsync(RegisterAddress, WriteValue.GetJTokenFromString(), DataType);
if (data.IsSuccess)
{
Plc.Logger?.LogInformation($" {WriteValue.GetJTokenFromString()} {Localizer["WriteSuccess"]}");
}
else
{
Plc.Logger?.Warning(data.ToString());
}
}
catch (Exception ex)
{
Plc.Logger?.Exception(ex);
}
}
}
}

View File

@@ -0,0 +1,121 @@
@using Microsoft.AspNetCore.Components.Web;
@using Microsoft.JSInterop;
@using ThingsGateway.Core.Json.Extension
@using ThingsGateway.Extension
@using ThingsGateway.Foundation
@using BootstrapBlazor.Components
@namespace ThingsGateway.Debug
@inherits AdapterDebugBase
<div class=@($"{ClassString} row my-2 mx-2") style="min-height:500px;height: 50%;">
<div class="col-12 col-md-5 h-100">
<Tab class="h-100">
<TabItem Text=@Localizer["CommonFunctions"]>
@if (ShowDefaultReadWriteContent)
{
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
ShowLabel="true" style="width:100%" />
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-8 p-1">
<div class="p-1">
<BootstrapInputNumber @bind-Value=@ArrayLength ShowLabel="true" />
<Select @bind-Value="@DataType" ShowLabel="true" IsPopover="true" />
</div>
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@Localizer["Read"]</Button>
</div>
</div>
<Divider />
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-8 p-1">
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
ShowLabel="true" />
</div>
<div class="col-12 col-md-4 p-1">
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@Localizer["Write"]</Button>
</div>
</div>
}
@if (ReadWriteContent != null)
{
@ReadWriteContent
}
</TabItem>
<TabItem Text=@Localizer["SpecialFunctions"]>
@if (ShowDefaultOtherContent)
{
@foreach (var item in VariableRunTimes)
{
<div class="row mx-1 form-inline mt-2">
<div class="col-12 col-md-8 p-1">
<div class="p-1">
<BootstrapInput @bind-Value=@item.RegisterAddress title="@Plc?.GetAddressDescription()"
ShowLabel="true" style="width:100%" />
<Select @bind-Value="@item.DataType" ShowLabel="true" IsPopover />
</div>
</div>
<div class="col-12 col-md-4 p-1">
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
</div>
</div>
}
<Divider />
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
}
</TabItem>
</Tab>
</div>
<div class="col-12 col-md-7 ">
<LogConsole LogLevel=@((Plc?.Logger??Logger)?.LogLevel??TouchSocket.Core.LogLevel.Trace) LogLevelChanged="(a)=>{
var log=Plc?.Logger??Logger;
if(log!=null)
log.LogLevel=a;
}" LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
</div>
</div>

View File

@@ -0,0 +1,129 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using ThingsGateway.Foundation;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
/// <inheritdoc/>
public partial class AdapterDebugComponent : AdapterDebugBase
{
/// <summary>
/// MaxPack
/// </summary>
public int MaxPack = 100;
/// <summary>
/// VariableRunTimes
/// </summary>
public List<VariableClass> VariableRunTimes;
[Parameter]
public string ClassString { get; set; }
[Parameter]
public string HeaderText { get; set; }
[Parameter, EditorRequired]
public string LogPath { get; set; }
[Parameter]
public ILog Logger { get; set; }
/// <summary>
/// 自定义模板
/// </summary>
[Parameter]
public RenderFragment OtherContent { get; set; }
/// <summary>
/// 自定义模板
/// </summary>
[Parameter]
public RenderFragment ReadWriteContent { get; set; }
[Parameter]
public bool ShowDefaultOtherContent { get; set; } = true;
[Parameter]
public bool ShowDefaultReadWriteContent { get; set; } = true;
[Inject]
private IStringLocalizer<AdapterDebugComponent> Localizer { get; set; }
/// <summary>
/// MulReadAsync
/// </summary>
/// <returns></returns>
public async Task MulRead()
{
if (Plc != null)
{
var deviceVariableSourceReads = Plc.LoadSourceRead<VariableSourceClass>(VariableRunTimes, MaxPack, "1000");
foreach (var item in deviceVariableSourceReads)
{
var result = await Plc.ReadAsync(item.RegisterAddress, item.Length);
if (result.IsSuccess)
{
try
{
var result1 = item.VariableRunTimes.PraseStructContent(Plc, result.Content, exWhenAny: true);
if (!result1.IsSuccess)
{
item.LastErrorMessage = result1.ErrorMessage;
var time = DateTime.Now;
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
Plc.Logger?.Warning(result1.ToString());
}
}
catch (Exception ex)
{
Plc.Logger?.Exception(ex);
}
}
else
{
item.LastErrorMessage = result.ErrorMessage;
var time = DateTime.Now;
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
Plc.Logger?.Warning(result.ToString());
}
}
}
}
/// <inheritdoc/>
protected override void OnInitialized()
{
VariableRunTimes = new()
{
new VariableClass()
{
DataType=DataTypeEnum.Int16,
RegisterAddress="40001",
IntervalTime="1000",
},
new VariableClass()
{
DataType=DataTypeEnum.Int32,
RegisterAddress="40011",
IntervalTime="1000",
},
};
HeaderText = Localizer[nameof(HeaderText)];
base.OnInitialized();
}
}

View File

@@ -0,0 +1,48 @@
@namespace ThingsGateway.Debug
@using Microsoft.AspNetCore.Components.Web;
@using System.IO.Ports;
@using ThingsGateway.Foundation
@using TouchSocket.Core
@using BootstrapBlazor.Components
<Card>
<BodyTemplate>
<ValidateForm Model="Model" OnValidSubmit="ValidSubmit">
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=3 LabelWidth=100 Model="Model" >
<FieldItems>
<EditorItem @bind-Field="@context.ChannelType">
<EditTemplate Context="value">
<div class="col-12 col-sm-6 col-md-4">
<Select SkipValidate="true" @bind-Value="@value.ChannelType" OnSelectedItemChanged=@((a)=>InvokeAsync(StateHasChanged)) />
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.RemoteUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
<EditorItem @bind-Field="@context.BindUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession&&context.ChannelType!=ChannelTypeEnum.TcpService) />
<EditorItem @bind-Field="@context.PortName" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.BaudRate" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.DataBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.Parity" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.StopBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
</FieldItems>
<Buttons>
<Button ButtonType="ButtonType.Submit" IsAsync class="mx-2" Color=Color.Primary OnClick="ConfimClick">@Localizer["Confim"]</Button>
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="ConnectClick">@Localizer["Connect"]</Button>
<Button IsAsync class="mx-2" Color=Color.Warning OnClick="DisconnectClick">@Localizer["Disconnect"]</Button>
</Buttons>
</EditorForm>
</ValidateForm>
</BodyTemplate>
</Card>

View File

@@ -0,0 +1,144 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Localization;
using ThingsGateway.Foundation;
using ThingsGateway.Razor;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
public partial class ChannelDataDebugComponent : ComponentBase
{
[Parameter]
public string ClassString { get; set; }
[Parameter]
public EventCallback<ChannelData> OnConnectClick { get; set; }
[Parameter]
public EventCallback<ChannelData> OnConfimClick { get; set; }
[Parameter]
public EventCallback OnDisConnectClick { get; set; }
private ChannelData? Model { get; set; } = new();
private IEnumerable<SelectedItem> ChannelDataItems { get; set; }
[Inject]
private IStringLocalizer<ChannelDataDebugComponent> Localizer { get; set; }
[Inject]
private ToastService ToastService { get; set; }
public Task ValidSubmit(EditContext editContext)
{
CheckInput(Model);
return Task.CompletedTask;
}
private void CheckInput(ChannelData input)
{
try
{
if (input.ChannelType == ChannelTypeEnum.TcpClient)
{
if (string.IsNullOrEmpty(input.RemoteUrl))
throw new(Localizer["RemoteUrlNotNull"]);
}
else if (input.ChannelType == ChannelTypeEnum.TcpService)
{
if (string.IsNullOrEmpty(input.BindUrl))
throw new(Localizer["BindUrlNotNull"]);
}
else if (input.ChannelType == ChannelTypeEnum.UdpSession)
{
if (string.IsNullOrEmpty(input.BindUrl) && string.IsNullOrEmpty(input.RemoteUrl))
throw new(Localizer["BindUrlOrRemoteUrlNotNull"]);
}
else if (input.ChannelType == ChannelTypeEnum.SerialPort)
{
if (string.IsNullOrEmpty(input.PortName))
throw new(Localizer["PortNameNotNull"]);
if (input.BaudRate == null)
throw new(Localizer["BaudRateNotNull"]);
if (input.DataBits == null)
throw new(Localizer["DataBitsNotNull"]);
if (input.Parity == null)
throw new(Localizer["ParityNotNull"]);
if (input.StopBits == null)
throw new(Localizer["StopBitsNotNull"]);
}
else
{
throw new(Localizer["NotOther"]);
}
}
catch (Exception ex)
{
ToastService.Warn(ex);
}
}
private async Task DisconnectClick()
{
if (Model?.Channel != null)
{
try
{
await Model.Channel.CloseAsync(DefaultResource.Localizer["ProactivelyDisconnect", nameof(DisconnectClick)]);
if (OnDisConnectClick.HasDelegate)
await OnDisConnectClick.InvokeAsync();
}
catch (Exception ex)
{
Model.Channel.Logger?.Exception(ex);
}
}
}
private async Task ConfimClick()
{
try
{
await ChannelData.CreateChannelAsync(Model);
if (OnConfimClick.HasDelegate)
await OnConfimClick.InvokeAsync(Model);
}
catch (Exception ex)
{
Model.Channel?.Logger?.Exception(ex);
}
}
private async Task ConnectClick()
{
if (Model != null)
{
try
{
if (Model.Channel != null)
if (OnConnectClick.HasDelegate)
await OnConnectClick.InvokeAsync(Model);
}
catch (Exception ex)
{
Model.Channel.Logger?.Exception(ex);
}
}
}
}

View File

@@ -0,0 +1,44 @@
@using Microsoft.AspNetCore.Components.Web;
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop;
@using ThingsGateway.NewLife.Threading
@using ThingsGateway.Extension;
@using BootstrapBlazor.Components
@namespace ThingsGateway.Debug
<Card HeaderText=@HeaderText class="mt-2" style="width:100%">
<HeaderTemplate>
<div class="flex-fill">
</div>
@if (LogLevelChanged.HasDelegate)
{
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
}
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(IsPause?"fa fa-play":"fa fa-pause") OnClick="Pause" />
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
</HeaderTemplate>
<BodyTemplate>
<div style=@($"height:{HeightString};overflow-y:scroll")>
<Virtualize Items="CurrentMessages??new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
<ItemContent>
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text ":"green--text ")
style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;"
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
</div>
@* </Tooltip> *@
</ItemContent>
</Virtualize>
</div>
</BodyTemplate>
</Card>

View File

@@ -0,0 +1,233 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System.Diagnostics;
using System.Text.RegularExpressions;
using ThingsGateway.Extension;
using ThingsGateway.Foundation;
using ThingsGateway.Razor;
using TouchSocket.Core;
namespace ThingsGateway.Debug;
public partial class LogConsole : IDisposable
{
private bool IsPause;
public bool Disposed { get; set; }
[Parameter, EditorRequired]
public LogLevel LogLevel { get; set; }
[Parameter]
public EventCallback<LogLevel> LogLevelChanged { get; set; }
[Parameter]
public string HeaderText { get; set; } = "Log";
[Parameter]
public string HeightString { get; set; } = "400px";
[Parameter, EditorRequired]
public string LogPath { get; set; }
/// <summary>
/// 日志
/// </summary>
public ICollection<LogMessage> Messages { get; set; } = new List<LogMessage>();
private ICollection<LogMessage> CurrentMessages => IsPause ? PauseMessagesText : Messages;
[Inject]
private DownloadService DownloadService { get; set; }
/// <summary>
/// 暂停缓存
/// </summary>
private ICollection<LogMessage> PauseMessagesText { get; set; } = new List<LogMessage>();
[Inject]
private IPlatformService PlatformService { get; set; }
protected override async Task OnParametersSetAsync()
{
Messages = new List<LogMessage>();
await ExecuteAsync();
await base.OnParametersSetAsync();
}
[Inject]
private ToastService ToastService { get; set; }
public void Dispose()
{
Disposed = true;
GC.SuppressFinalize(this);
}
protected async Task ExecuteAsync()
{
try
{
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
if (files == null || files.FirstOrDefault() == null || !files.FirstOrDefault().IsSuccess)
{
Messages = new List<LogMessage>();
await Task.Delay(1000);
}
else
{
await Task.Run(async () =>
{
Stopwatch sw = Stopwatch.StartNew();
var result = TextFileReader.LastLog(files.FirstOrDefault().FullName, 0);
if (result.IsSuccess)
{
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
}
else
{
Messages = new List<LogMessage>();
}
sw.Stop();
if (sw.ElapsedMilliseconds > 500)
{
await Task.Delay(1000);
}
});
}
}
}
catch (Exception ex)
{
System.Console.WriteLine(ex);
}
}
protected override void OnInitialized()
{
_ = RunTimerAsync();
base.OnInitialized();
}
private async Task Delete()
{
if (LogPath != null)
{
var files = TextFileReader.GetFiles(LogPath);
if (files == null || files.FirstOrDefault() == null || !files.FirstOrDefault().IsSuccess)
{
}
else
{
foreach (var item in files)
{
if (File.Exists(item.FullName))
{
int error = 0;
while (error < 3)
{
try
{
File.SetAttributes(item.FullName, FileAttributes.Normal);
File.Delete(item.FullName);
break;
}
catch
{
await Task.Delay(3000);
error++;
}
}
}
}
}
}
}
private async Task HandleOnExportClick(MouseEventArgs args)
{
try
{
if (IsPause)
{
using var memoryStream = new MemoryStream();
using StreamWriter writer = new(memoryStream);
foreach (var item in PauseMessagesText)
{
await writer.WriteLineAsync(item.Message);
}
await writer.FlushAsync();
memoryStream.Seek(0, SeekOrigin.Begin);
// 定义文件名称规则的正则表达式模式
string pattern = @"[\\/:*?""<>|]";
// 使用正则表达式将不符合规则的部分替换为下划线
string sanitizedFileName = Regex.Replace(HeaderText, pattern, "_");
await DownloadService.DownloadFromStreamAsync(sanitizedFileName + DateTime.Now.ToFileDateTimeFormat(), memoryStream);
}
else
{
if (PlatformService != null)
await PlatformService.OnLogExport(LogPath);
}
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
private Task Pause()
{
IsPause = !IsPause;
if (IsPause)
PauseMessagesText = Messages.ToList();
return Task.CompletedTask;
}
private async Task RunTimerAsync()
{
while (!Disposed)
{
try
{
await ExecuteAsync();
await InvokeAsync(StateHasChanged);
await Task.Delay(1000);
}
catch (Exception ex)
{
System.Console.WriteLine(ex);
}
}
}
}
public class LogMessage
{
public LogMessage(int level, string message)
{
Level = level;
Message = message;
}
public int Level { get; set; }
public string Message { get; set; }
}

View File

@@ -0,0 +1,68 @@
{
"ThingsGateway.Debug.ChannelDataDebugComponent": {
"Name": "Name",
"Name.Required": "{0} cannot be empty",
"ChannelType": "Channel Type",
"Enable": "Enable",
"LogEnable": "Enable Debug Log",
"RemoteUrl": "Remote IP Address",
"BindUrl": "Local Bind IP Address",
"PortName": "COM Port",
"BaudRate": "Baud Rate",
"DataBits": "Data Bits",
"Parity": "Parity",
"StopBits": "Stop Bits",
"DtrEnable": "Dtr",
"RtsEnable": "Rts",
"RemoteUrlNotNull": "Remote IP Address cannot be empty",
"BindUrlNotNull": "Local Bind IP Address cannot be empty",
"BindUrlOrRemoteUrlNotNull": "Remote IP Address or Local Bind IP Address cannot be empty",
"PortNameNotNull": "COM Port cannot be empty",
"BaudRateNotNull": "Baud Rate cannot be empty",
"DataBitsNotNull": "Data Bits can be empty",
"ParityNotNull": "Parity cannot be empty",
"StopBitsNotNull": "Stop Bits cannot be empty",
"SaveChannel": "Add/Modify Channel",
"DeleteChannel": "Delete Channel",
"ClearChannel": "Clear Channel",
"ExportChannel": "Export Channel",
"ImportChannel": "Import Channel",
"ImportNullError": "Unable to recognize",
"NotOther": "Not supporting other channel types",
"Connect": "Connect",
"Confim": "Confim",
"Disconnect": "Disconnect",
"Channel": "Channel"
},
"ThingsGateway.Debug.AdapterDebugBase": {
"WriteSuccess": "Write Successful",
"DefaultSend": "Direct Send",
"Send": "Send",
"DataType": "Data Type",
"RegisterAddress": "Register Address",
"ArrayLength": "Array Length",
"WriteValue": "Write Value",
"SendValue": "Send Raw Message"
},
"ThingsGateway.Debug.AdapterDebugComponent": {
"HeaderText": "Channel Log",
"WriteSuccess": "Write Successful",
"DefaultSend": "Direct Send",
"Send": "Send",
"DataType": "Data Type",
"RegisterAddress": "Register Address",
"ArrayLength": "Array Length",
"WriteValue": "Write Value",
"SendValue": "Send Raw Message",
"CommonFunctions": "Common Functions",
"SpecialFunctions": "Special Functions",
"Read": "Read",
"Write": "Write",
"MulRead": "Multiple Read"
},
"ThingsGateway.Debug.ChannelDataEditComponent": {
"BasicInformation": "Basic Information",
"Connection": "Connection"
}
}

View File

@@ -0,0 +1,75 @@
{
"ThingsGateway.Debug.ChannelDataDebugComponent": {
"Name": "名称",
"Name.Required": " {0} 不可为空",
"ChannelType": "通道类型",
"Enable": "启用",
"LogEnable": "启用调试日志",
"RemoteUrl": "远程IP地址",
"BindUrl": "本地绑定IP地址",
"PortName": "COM口",
"BaudRate": "波特率",
"DataBits": "数据位",
"Parity": "校验位",
"StopBits": "停止位",
"DtrEnable": "Dtr",
"RtsEnable": "Rts",
"RemoteUrlNotNull": "远程IP地址不可为空",
"BindUrlNotNull": "本地绑定IP地址不可为空",
"BindUrlOrRemoteUrlNotNull": "远程IP地址或本地绑定IP地址不可为空",
"PortNameNotNull": "COM口不可为空",
"BaudRateNotNull": "波特率不可为空",
"DataBitsNotNull": "数据位可为空",
"ParityNotNull": "校验位不可为空",
"StopBitsNotNull": "停止位不可为空",
"SaveChannel": "添加/修改通道",
"DeleteChannel": "删除通道",
"ClearChannel": "清空通道",
"ExportChannel": "导出通道",
"ImportChannel": "导入通道",
"ImportNullError": "无法识别",
"NotOther": "不支持其他通道类型",
"Connect": "连接",
"Confim": "创建",
"Disconnect": "断开",
"Channel": "通道"
},
"ThingsGateway.Debug.AdapterDebugBase": {
"WriteSuccess": "写入成功",
"DefaultSend": "直接发送",
"Send": "发送",
"DataType": "数据类型",
"RegisterAddress": "寄存器地址",
"ArrayLength": "数组长度",
"WriteValue": "写入值",
"SendValue": "发送原始报文"
},
"ThingsGateway.Debug.AdapterDebugComponent": {
"HeaderText": "通道日志",
"WriteSuccess": "写入成功",
"DefaultSend": "直接发送",
"Send": "发送",
"DataType": "数据类型",
"RegisterAddress": "寄存器地址",
"ArrayLength": "数组长度",
"WriteValue": "写入值",
"SendValue": "发送原始报文",
"CommonFunctions": "常用功能",
"SpecialFunctions": "特殊功能",
"Read": "读取",
"Write": "写入",
"MulRead": "多读"
},
"ThingsGateway.Debug.ChannelDataEditComponent": {
"BasicInformation": "基础信息",
"Connection": "连接"
}
}

View File

@@ -0,0 +1,67 @@
{
"ThingsGateway.Debug.ChannelDataDebugComponent": {
"Name": "名稱",
"Name.Required": "{0} 不可為空",
"ChannelType": "通道類型",
"Enable": "啟用",
"LogEnable": "啟用調試日誌",
"RemoteUrl": "遠程IP地址",
"BindUrl": "本地綁定IP地址",
"PortName": "COM口",
"BaudRate": "波特率",
"DataBits": "數據位",
"Parity": "校驗位",
"StopBits": "停止位",
"DtrEnable": "Dtr",
"RtsEnable": "Rts",
"RemoteUrlNotNull": "遠程IP地址不可為空",
"BindUrlNotNull": "本地綁定IP地址不可為空",
"BindUrlOrRemoteUrlNotNull": "遠程IP地址或本地綁定IP地址不可為空",
"PortNameNotNull": "COM口不可為空",
"BaudRateNotNull": "波特率不可為空",
"DataBitsNotNull": "數據位可為空",
"ParityNotNull": "校驗位不可為空",
"StopBitsNotNull": "停止位不可為空",
"SaveChannel": "添加/修改通道",
"DeleteChannel": "刪除通道",
"ClearChannel": "清空通道",
"ExportChannel": "導出通道",
"ImportChannel": "導入通道",
"ImportNullError": "無法識別",
"NotOther": "不支持其他通道類型",
"Connect": "連接",
"Confim": "創建",
"Disconnect": "斷開",
"Channel": "通道"
},
"ThingsGateway.Debug.AdapterDebugBase": {
"WriteSuccess": "寫入成功",
"DefaultSend": "直接發送",
"Send": "發送",
"DataType": "數據類型",
"RegisterAddress": "寄存器地址",
"ArrayLength": "數組長度",
"WriteValue": "寫入值",
"SendValue": "發送原始報文"
},
"ThingsGateway.Debug.AdapterDebugComponent": {
"HeaderText": "通道日誌",
"WriteSuccess": "寫入成功",
"DefaultSend": "直接發送",
"Send": "發送",
"DataType": "數據類型",
"RegisterAddress": "寄存器地址",
"ArrayLength": "數組長度",
"WriteValue": "寫入值",
"SendValue": "發送原始報文",
"CommonFunctions": "常用功能",
"SpecialFunctions": "特殊功能",
"Read": "讀取",
"Write": "寫入",
"MulRead": "多讀"
},
"ThingsGateway.Debug.ChannelDataEditComponent": {
"BasicInformation": "基礎資訊",
"Connection": "連接"
}
}

View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Debug;
public class HybridPlatformService : IPlatformService
{
public Task OnLogExport(string logPath)
{
OpenFolder(logPath);
return Task.CompletedTask;
}
private static void OpenFolder(string path)
{
// Normalize the path for the current operating system
path = System.IO.Path.GetFullPath(path); // Ensure the path is absolute
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
{
System.Diagnostics.Process.Start("explorer.exe", path);
}
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
{
System.Diagnostics.Process.Start("xdg-open", path);
}
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX))
{
System.Diagnostics.Process.Start("open", path);
}
}
}

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Debug;
public interface IPlatformService
{
/// <summary>
/// OnLogExport
/// </summary>
/// <param name="logPath">日志文件夹路径</param>
/// <returns></returns>
public Task OnLogExport(string logPath);
}

View File

@@ -12,6 +12,7 @@ using Microsoft.JSInterop;
using ThingsGateway.Extension;
using ThingsGateway.Foundation;
using ThingsGateway.Razor;
namespace ThingsGateway.Debug;

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.Extensions.DependencyInjection;
namespace ThingsGateway.Debug;
[AppStartup(100000000)]
public class Startup : AppStartup
{
public void ConfigureAdminApp(IServiceCollection services)
{
services.AddScoped<IPlatformService, PlatformService>();
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="$(SolutionDir)FoundationVersion.props" />
<Import Project="$(SolutionDir)PackNuget.props" />
<ItemGroup>
<Content Remove="Locales\*.json" />
<EmbeddedResource Include="Locales\*.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Admin\src\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
global using BootstrapBlazor.Components;
global using Microsoft.AspNetCore.Components;
global using Microsoft.Extensions.Localization;
global using Microsoft.Extensions.Options;
global using System.Diagnostics.CodeAnalysis;
global using ThingsGateway.Razor;
[assembly: SuppressMessage("Reliability", "CA2007", Justification = "<挂起>", Scope = "module")]

View File

@@ -7,4 +7,3 @@
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,121 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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
// 感谢您的下载和使用
//------------------------------------------------------------------------------
#if !NET45_OR_GREATER
using Microsoft.CodeAnalysis;
namespace ThingsGateway.Foundation;
internal static class Utils
{
public static bool IsInheritFrom(this ITypeSymbol typeSymbol, string baseType)
{
if (typeSymbol.ToDisplayString() == baseType)
{
return true;
}
if (typeSymbol.BaseType != null)
{
var b = IsInheritFrom(typeSymbol.BaseType, baseType);
if (b)
{
return true;
}
}
foreach (var item in typeSymbol.AllInterfaces)
{
var b = IsInheritFrom(item, baseType);
if (b)
{
return true;
}
}
return false;
}
public static string RenameCamelCase(this string str)
{
var firstChar = str[0];
if (firstChar == char.ToLowerInvariant(firstChar))
{
return str;
}
var name = str.ToCharArray();
name[0] = char.ToLowerInvariant(firstChar);
return new string(name);
}
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute)
{
foreach (var attr in symbol.GetAttributes())
{
var attrClass = attr.AttributeClass;
if (attrClass != null && attrClass.ToDisplayString() == attribute.ToDisplayString())
{
return true;
}
}
return false;
}
public static bool HasAttribute(this ISymbol symbol, string attribute, out AttributeData attributeData)
{
foreach (var attr in symbol.GetAttributes())
{
var attrClass = attr.AttributeClass;
if (attrClass != null && attrClass.ToDisplayString() == attribute)
{
attributeData = attr;
return true;
}
}
attributeData = default;
return false;
}
public static bool HasFlags(int value, int flag)
{
return (value & flag) == flag;
}
public static bool HasReturn(this IMethodSymbol method)
{
if (method.ReturnsVoid || method.ReturnType.ToDisplayString() == typeof(Task).FullName)
{
return false;
}
return true;
}
}
#endif

View File

@@ -0,0 +1,128 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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
// 感谢您的下载和使用
//------------------------------------------------------------------------------
#if !NET45_OR_GREATER
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Reflection;
using System.Text;
namespace ThingsGateway.Foundation;
internal sealed class VariableCodeBuilder
{
private readonly INamedTypeSymbol m_pluginClass;
public VariableCodeBuilder(INamedTypeSymbol pluginClass)
{
m_pluginClass = pluginClass;
}
public string Prefix { get; set; }
public IEnumerable<string> Usings
{
get
{
yield return "using System;";
yield return "using System.Diagnostics;";
yield return "using ThingsGateway.Foundation;";
yield return "using System.Threading.Tasks;";
}
}
public string GetFileName()
{
return m_pluginClass.ToDisplayString() + "Generator";
}
public bool TryToSourceText(out SourceText sourceText)
{
var code = ToString();
if (string.IsNullOrEmpty(code))
{
sourceText = null;
return false;
}
sourceText = SourceText.From(code, Encoding.UTF8);
return true;
}
public override string ToString()
{
var propertys = FindPropertys().ToList();
if (propertys.Count == 0)
{
return null;
}
var codeString = new StringBuilder();
codeString.AppendLine("/*");
codeString.AppendLine("此代码由SourceGenerator工具直接生成非必要请不要修改此处代码");
codeString.AppendLine("*/");
codeString.AppendLine("#pragma warning disable");
foreach (var item in Usings)
{
codeString.AppendLine(item);
}
codeString.AppendLine($"namespace {m_pluginClass.ContainingNamespace}");
codeString.AppendLine("{");
codeString.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCode(\"ThingsGateway.Foundation\",\"{Assembly.GetExecutingAssembly().GetName().Version}\")]");
codeString.AppendLine($"partial class {m_pluginClass.Name}");
codeString.AppendLine("{");
foreach (var item in propertys)
{
BuildMethod(codeString, item);
}
codeString.AppendLine("}");
codeString.AppendLine("}");
return codeString.ToString();
}
private void BuildMethod(StringBuilder stringBuilder, IPropertySymbol propertySymbol)
{
var attributeData = propertySymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName);
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public ValueTask<OperResult> Write{propertySymbol.Name}Async({propertySymbol.Type} value,CancellationToken cancellationToken=default)");
stringBuilder.AppendLine("{");
stringBuilder.AppendLine($"return WriteValueAsync(\"{propertySymbol.Name}\",value,cancellationToken);");
stringBuilder.AppendLine("}");
stringBuilder.AppendLine();
}
private IEnumerable<IPropertySymbol> FindPropertys()
{
return m_pluginClass
.GetMembers()
.OfType<IPropertySymbol>()
.Where(m =>
{
return m.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName);
});
}
}
#endif

View File

@@ -0,0 +1,88 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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
// 感谢您的下载和使用
//------------------------------------------------------------------------------
#if !NET45_OR_GREATER
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace ThingsGateway.Foundation;
/// <summary>
/// 源生成
/// </summary>
[Generator]
public class VariableObjectSourceGenerator : ISourceGenerator
{
private string m_generatorVariableAttribute = @"
using System;
namespace ThingsGateway.Foundation
{
/// <summary>
/// 使用源生成变量写入方法的调用。
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
internal class GeneratorVariableAttribute:Attribute
{
}
}
";
/// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context)
{
//Debugger.Launch();
context.RegisterForPostInitialization(a =>
{
a.AddSource(nameof(m_generatorVariableAttribute), m_generatorVariableAttribute);
});
context.RegisterForSyntaxNotifications(() => new VariableSyntaxReceiver());
}
/// <inheritdoc/>
public void Execute(GeneratorExecutionContext context)
{
var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly);
if (context.SyntaxReceiver is VariableSyntaxReceiver receiver)
{
var builders = receiver
.GetVariableObjectTypes(context.Compilation)
.Select(i => new VariableCodeBuilder(i))
.Distinct();
foreach (var builder in builders)
{
if (builder.TryToSourceText(out var sourceText))
{
var tree = CSharpSyntaxTree.ParseText(sourceText);
var root = tree.GetRoot().NormalizeWhitespace();
var ret = root.ToFullString();
context.AddSource($"{builder.GetFileName()}.g.cs", ret);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,116 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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
// 感谢您的下载和使用
//------------------------------------------------------------------------------
#if !NET45_OR_GREATER
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace ThingsGateway.Foundation;
internal sealed class VariableSyntaxReceiver : ISyntaxReceiver
{
public const string GeneratorVariableAttributeTypeName = "ThingsGateway.Foundation.GeneratorVariableAttribute";
public const string VariableRuntimeAttributeTypeName = "ThingsGateway.Foundation.VariableRuntimeAttribute";
/// <summary>
/// 接口列表
/// </summary>
private readonly List<ClassDeclarationSyntax> m_classSyntaxList = new List<ClassDeclarationSyntax>();
/// <summary>
/// 访问语法树
/// </summary>
/// <param name="syntaxNode"></param>
void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax syntax)
{
m_classSyntaxList.Add(syntax);
}
}
public INamedTypeSymbol GeneratorVariableAttributeAttribute { get; private set; }
/// <summary>
/// 获取所有插件符号
/// </summary>
/// <param name="compilation"></param>
/// <returns></returns>
public IEnumerable<INamedTypeSymbol> GetVariableObjectTypes(Compilation compilation)
{
GeneratorVariableAttributeAttribute = compilation.GetTypeByMetadataName(GeneratorVariableAttributeTypeName)!;
if (GeneratorVariableAttributeAttribute == null)
{
yield break;
}
foreach (var classSyntax in m_classSyntaxList)
{
var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax);
if (@class != null && IsVariableObject(@class))
{
yield return @class;
}
}
}
/// <summary>
/// 是否为变量类
/// </summary>
/// <param name="class"></param>
/// <returns></returns>
public bool IsVariableObject(INamedTypeSymbol @class)
{
if (GeneratorVariableAttributeAttribute is null)
{
return false;
}
if (@class.IsAbstract)
{
return false;
}
return HasAttribute(@class, GeneratorVariableAttributeAttribute);
}
/// <summary>
/// 返回是否声明指定的特性
/// </summary>
/// <param name="symbol"></param>
/// <param name="attribute"></param>
/// <returns></returns>
public static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attribute)
{
foreach (var attr in symbol.GetAttributes())
{
var attrClass = attr.AttributeClass;
if (attrClass != null && (attrClass.AllInterfaces.Contains(attribute) || SymbolEqualityComparer.Default.Equals(attrClass, attribute)))
{
return true;
}
}
return false;
}
}
#endif

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoPackageAnalysis>true</NoPackageAnalysis>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,49 @@
param($installPath, $toolsPath, $package, $project)
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
# Install the language agnostic analyzers.
if (Test-Path $analyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Install language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}

View File

@@ -0,0 +1,56 @@
param($installPath, $toolsPath, $package, $project)
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
# Uninstall the language agnostic analyzers.
if (Test-Path $analyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Uninstall language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
{
if($project.Object.AnalyzerReferences)
{
try
{
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
}
catch
{
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
global using ThingsGateway.Foundation;
global using TouchSocket.Core;

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)PackNuget.props" />
<Import Project="$(SolutionDir)FoundationVersion.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net9.0;net8.0;net6.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj">
<Private>false</Private>
<IncludeAssets> none;</IncludeAssets>
</ProjectReference>
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
<ProjectReference Include="..\ThingsGateway.CSScript\ThingsGateway.CSScript.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\ThingsGateway.Foundation.SourceGenerator\tools\*.ps1" PackagePath="tools" Pack="true" Visible="false" />
<None Include="..\ThingsGateway.Foundation.SourceGenerator\bin\$(Configuration)\netstandard2.0\ThingsGateway.Foundation.SourceGenerator.dll" PackagePath="analyzers\dotnet\cs" Pack="true" Visible="false" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,245 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq.Expressions;
using ThingsGateway.Gateway.Application.Extensions;
using ThingsGateway.NewLife.Reflection;
namespace ThingsGateway.Foundation;
/// <summary>
/// VariableObject
/// </summary>
public abstract class VariableObject
{
/// <summary>
/// 协议对象
/// </summary>
[JsonIgnore]
public IProtocol Protocol;
/// <summary>
/// VariableRuntimePropertyDict
/// </summary>
[JsonIgnore]
public Dictionary<string, VariableRuntimeProperty>? VariableRuntimePropertyDict;
/// <summary>
/// DeviceVariableSourceReads
/// </summary>
[JsonIgnore]
protected List<VariableSourceClass>? DeviceVariableSourceReads;
/// <summary>
/// MaxPack
/// </summary>
protected int MaxPack;
/// <summary>
/// VariableObject
/// </summary>
public VariableObject(IProtocol protocol, int maxPack)
{
Protocol = protocol;
MaxPack = maxPack;
}
/// <summary>
/// ReadTime
/// </summary>
public DateTime ReadTime { get; set; }
/// <summary>
/// GetExpressionsValue
/// </summary>
/// <param name="value"></param>
/// <param name="variableRuntimeProperty"></param>
/// <returns></returns>
public virtual JToken GetExpressionsValue(object value, VariableRuntimeProperty variableRuntimeProperty)
{
var jToken = JToken.FromObject(value);
if (!string.IsNullOrEmpty(variableRuntimeProperty.Attribute.WriteExpressions))
{
object rawdata = jToken is JValue jValue ? jValue.Value : jToken is JArray jArray ? jArray : jToken.ToString();
object data = variableRuntimeProperty.Attribute.WriteExpressions.GetExpressionsResult(rawdata);
jToken = JToken.FromObject(data);
}
return jToken;
}
/// <summary>
/// GetBytes
/// </summary>
/// <returns></returns>
public virtual byte[] GetBytes(Expression<Func<object>> accessor)
{
if (accessor.Body == null)
{
throw new ArgumentNullException(nameof(accessor));
}
var expression = accessor.Body;
if (expression is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert && unaryExpression.Type == typeof(object))
{
expression = unaryExpression.Operand;
}
if (expression is not MemberExpression memberExpression)
{
throw new ArgumentException("Can only access properties");
}
// 从字典中获取与属性对应的变量信息
if (!VariableRuntimePropertyDict.TryGetValue(memberExpression.Member.Name, out var variable))
{
throw new KeyNotFoundException($"Variable for {memberExpression.Member.Name} not found.");
}
var func = accessor.Compile();
return variable.VariableClass.ThingsGatewayBitConverter.GetBytesFormData(GetExpressionsValue(func(), variable), variable.VariableClass.DataType);
}
/// <summary>
/// GetVariableClass
/// </summary>
/// <returns></returns>
public virtual List<VariableClass> GetVariableClass()
{
VariableRuntimePropertyDict ??= VariableObjectHelper.GetVariableRuntimePropertyDict(GetType());
List<VariableClass> variableClasss = new();
foreach (var pair in VariableRuntimePropertyDict)
{
var dataType = pair.Value.Attribute.DataType == DataTypeEnum.Object ? Type.GetTypeCode(pair.Value.Property.PropertyType.IsArray ? pair.Value.Property.PropertyType.GetElementType() : pair.Value.Property.PropertyType).GetDataType() : pair.Value.Attribute.DataType;
VariableClass variableClass = new VariableClass()
{
DataType = dataType,
RegisterAddress = pair.Value.Attribute.RegisterAddress,
IntervalTime = "1000",
};
pair.Value.VariableClass = variableClass;
variableClasss.Add(variableClass);
}
return variableClasss;
}
/// <summary>
/// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性
/// </summary>
public virtual async ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default)
{
try
{
GetVariableSources();
//连读
foreach (var item in DeviceVariableSourceReads)
{
var result = await Protocol.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false);
if (result.IsSuccess)
{
var result1 = item.VariableRunTimes.PraseStructContent(Protocol, result.Content, exWhenAny: true);
if (!result1.IsSuccess)
{
item.LastErrorMessage = result1.ErrorMessage;
var time = DateTime.Now;
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
return new OperResult(result1);
}
}
else
{
item.LastErrorMessage = result.ErrorMessage;
var time = DateTime.Now;
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
return new OperResult(result);
}
}
SetValue();
return OperResult.Success;
}
catch (Exception ex)
{
return new OperResult(ex);
}
}
/// <summary>
/// 结果反射赋值
/// </summary>
public virtual void SetValue()
{
//结果反射赋值
foreach (var pair in VariableRuntimePropertyDict)
{
if (!string.IsNullOrEmpty(pair.Value.Attribute.ReadExpressions))
{
var data = pair.Value.Attribute.ReadExpressions.GetExpressionsResult(pair.Value.VariableClass.Value);
pair.Value.Property.SetValue(this, data.ChangeType(pair.Value.Property.PropertyType));
}
else
{
pair.Value.Property.SetValue(this, pair.Value.VariableClass.Value.ChangeType(pair.Value.Property.PropertyType));
}
}
ReadTime = DateTime.Now;
}
/// <summary>
/// 写入值到设备中
/// </summary>
/// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param>
/// <param name="value">写入值</param>
/// <param name="cancellationToken">取消令箭</param>
public virtual async ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default)
{
try
{
GetVariableSources();
if (string.IsNullOrEmpty(propertyName))
{
return new OperResult($"PropertyName cannot be null or empty.");
}
if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty))
{
return new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute");
}
JToken jToken = GetExpressionsValue(value, variableRuntimeProperty);
var result = await Protocol.WriteAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false);
return result;
}
catch (Exception ex)
{
return new OperResult(ex);
}
}
/// <summary>
/// GetVariableSources
/// </summary>
protected virtual void GetVariableSources()
{
if (DeviceVariableSourceReads == null)
{
List<VariableClass> variableClasss = GetVariableClass();
DeviceVariableSourceReads = Protocol.LoadSourceRead<VariableSourceClass>(variableClasss, MaxPack, "1000");
}
}
}

View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Reflection;
/// <summary>
/// VariableObjectHelper
/// </summary>
public static class VariableObjectHelper
{
/// <summary>
/// GetVariableRuntimePropertyDict
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static Dictionary<string, VariableRuntimeProperty> GetVariableRuntimePropertyDict(Type type)
{
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
var dictionary = new Dictionary<string, VariableRuntimeProperty>();
foreach (var propertyInfo in properties)
{
VariableRuntimeAttribute variableRuntimeAttribute = propertyInfo.GetCustomAttribute<VariableRuntimeAttribute>();
if (variableRuntimeAttribute == null)
{
continue;
}
dictionary.Add(propertyInfo.Name, new VariableRuntimeProperty(variableRuntimeAttribute, propertyInfo));
}
return dictionary;
}
}

View File

@@ -0,0 +1,38 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 变量特性
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class VariableRuntimeAttribute : Attribute
{
/// <summary>
/// 数据类型默认不填时会使用属性的Type
/// </summary>
public DataTypeEnum DataType { get; set; }
/// <summary>
/// 读取表达式
/// </summary>
public string? ReadExpressions { get; set; }
/// <summary>
/// 寄存器地址
/// </summary>
public string? RegisterAddress { get; set; }
/// <summary>
/// 写入表达式
/// </summary>
public string? WriteExpressions { get; set; }
}

View File

@@ -0,0 +1,45 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Reflection;
namespace ThingsGateway.Foundation;
/// <summary>
/// VariableRuntimeProperty
/// </summary>
public class VariableRuntimeProperty
{
/// <summary>
/// VariableRuntimeProperty
/// </summary>
/// <param name="attribute"></param>
/// <param name="property"></param>
public VariableRuntimeProperty(VariableRuntimeAttribute attribute, PropertyInfo property)
{
Attribute = attribute;
Property = property;
}
/// <summary>
/// Attribute
/// </summary>
public VariableRuntimeAttribute Attribute { get; }
/// <summary>
/// Property
/// </summary>
public PropertyInfo Property { get; }
/// <summary>
/// VariableClass
/// </summary>
public VariableClass VariableClass { get; set; }
}

View File

@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json;
using System.IO.Ports;
using TouchSocket.SerialPorts;
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
public class ChannelData
{
/// <inheritdoc/>
public long Id { get; set; } = IncrementCount.GetCurrentValue();
/// <inheritdoc/>
public virtual ChannelTypeEnum ChannelType { get; set; }
/// <summary>
/// 远程地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
/// </summary>
public string? RemoteUrl { get; set; } = "127.0.0.1:502";
/// <summary>
/// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
/// </summary>
public string? BindUrl { get; set; }
/// <summary>
/// COM
/// </summary>
public string? PortName { get; set; } = "COM1";
/// <summary>
/// 波特率
/// </summary>
public int? BaudRate { get; set; } = 9600;
/// <summary>
/// 数据位
/// </summary>
public int? DataBits { get; set; } = 8;
/// <summary>
/// 校验位
/// </summary>
public Parity? Parity { get; set; } = System.IO.Ports.Parity.None;
/// <summary>
/// 停止位
/// </summary>
public StopBits? StopBits { get; set; } = System.IO.Ports.StopBits.One;
/// <summary>
/// DtrEnable
/// </summary>
public bool? DtrEnable { get; set; } = true;
/// <summary>
/// RtsEnable
/// </summary>
public bool? RtsEnable { get; set; } = true;
/// <summary>
/// TouchSocketConfig
/// </summary>
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonIgnore]
#endif
[JsonIgnore]
public TouchSocketConfig TouchSocketConfig;
/// <summary>
/// Channel
/// </summary>
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonIgnore]
#endif
[JsonIgnore]
public IChannel Channel;
private static IncrementCount IncrementCount = new(long.MaxValue);
/// <summary>
/// 创建通道
/// </summary>
/// <param name="channelData"></param>
public static async Task CreateChannelAsync(ChannelData channelData)
{
if (channelData.Channel != null)
{
channelData.Channel.Close();
channelData.Channel.SafeDispose();
}
channelData.TouchSocketConfig?.Dispose();
channelData.TouchSocketConfig = new TouchSocket.Core.TouchSocketConfig();
var logMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
var logger = TextFileLogger.CreateTextLogger(channelData.Id.GetDebugLogPath());
logger.LogLevel = LogLevel.Trace;
logMessage.AddLogger(logger);
channelData.TouchSocketConfig.ConfigureContainer(a => a.RegisterSingleton<ILog>(logMessage));
switch (channelData.ChannelType)
{
case ChannelTypeEnum.TcpClient:
channelData.Channel = await channelData.TouchSocketConfig.GetTcpClientWithIPHostAsync(channelData.RemoteUrl, channelData.BindUrl).ConfigureAwait(false);
break;
case ChannelTypeEnum.TcpService:
channelData.Channel = await channelData.TouchSocketConfig.GetTcpServiceWithBindIPHostAsync(channelData.BindUrl).ConfigureAwait(false);
break;
case ChannelTypeEnum.SerialPort:
channelData.Channel = await channelData.TouchSocketConfig.GetSerialPortWithOptionAsync(channelData.Map<SerialPortOption>()).ConfigureAwait(false);
break;
case ChannelTypeEnum.UdpSession:
channelData.Channel = await channelData.TouchSocketConfig.GetUdpSessionWithIPHostAsync(channelData.RemoteUrl, channelData.BindUrl).ConfigureAwait(false);
break;
}
}
}

View File

@@ -0,0 +1,201 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Foundation.Extension.String;
using TouchSocket.SerialPorts;
namespace ThingsGateway.Foundation;
/// <summary>
/// 通道扩展
/// </summary>
public static class ChannelConfigExtensions
{
/// <summary>
/// 触发通道接收事件
/// </summary>
/// <param name="clientChannel">通道</param>
/// <param name="e">接收数据</param>
/// <param name="funcs">事件</param>
/// <returns></returns>
internal static async Task OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
{
clientChannel.ThrowIfNull(nameof(IClientChannel));
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
if (funcs.Count > 0)
{
foreach (var func in funcs)
{
await func.Invoke(clientChannel, e).ConfigureAwait(false);
if (e.Handled)
{
break;
}
}
}
}
/// <summary>
/// 触发通道事件
/// </summary>
/// <param name="clientChannel">通道</param>
/// <param name="funcs">事件</param>
/// <returns></returns>
internal static async Task OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
{
clientChannel.ThrowIfNull(nameof(IClientChannel));
funcs.ThrowIfNull(nameof(ChannelEventHandler));
if (funcs.Count > 0)
{
foreach (var func in funcs)
{
var handled = await func.Invoke(clientChannel).ConfigureAwait(false);
if (handled)
{
break;
}
}
}
}
/// <summary>
/// 获取一个新的通道。传入通道类型,远程服务端地址,绑定地址,串口配置信息
/// </summary>
/// <param name="config">配置</param>
/// <param name="channelType">通道类型</param>
/// <param name="remoteUrl">远端IP端口配置</param>
/// <param name="bindUrl">本地IP端口配置</param>
/// <param name="serialPortOption">串口配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static async ValueTask<IChannel?> GetChannelAsync(this TouchSocketConfig config, ChannelTypeEnum channelType, string? remoteUrl = default, string? bindUrl = default, SerialPortOption? serialPortOption = default)
{
config.ThrowIfNull(nameof(TouchSocketConfig));
channelType.ThrowIfNull(nameof(ChannelTypeEnum));
switch (channelType)
{
case ChannelTypeEnum.TcpClient:
remoteUrl.ThrowIfNull(nameof(IPHost));
return await config.GetTcpClientWithIPHostAsync(remoteUrl, bindUrl).ConfigureAwait(false);
case ChannelTypeEnum.TcpService:
bindUrl.ThrowIfNull(nameof(IPHost));
return await config.GetTcpServiceWithBindIPHostAsync(bindUrl).ConfigureAwait(false);
case ChannelTypeEnum.SerialPort:
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
return await config.GetSerialPortWithOptionAsync(serialPortOption).ConfigureAwait(false);
case ChannelTypeEnum.UdpSession:
if (string.IsNullOrEmpty(remoteUrl) && string.IsNullOrEmpty(bindUrl))
throw new ArgumentNullException(nameof(IPHost));
return await config.GetUdpSessionWithIPHostAsync(remoteUrl, bindUrl).ConfigureAwait(false);
}
return default;
}
/// <summary>
/// 获取一个新的串口通道。传入串口配置信息
/// </summary>
/// <param name="config">配置</param>
/// <param name="serialPortOption">串口配置</param>
/// <returns></returns>
public static async ValueTask<SerialPortChannel> GetSerialPortWithOptionAsync(this TouchSocketConfig config, SerialPortOption serialPortOption)
{
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
config.SetSerialPortOption(serialPortOption);
//载入配置
SerialPortChannel serialPortChannel = new SerialPortChannel();
await serialPortChannel.SetupAsync(config).ConfigureAwait(false);
return serialPortChannel;
}
/// <summary>
/// 获取一个新的Tcp客户端通道。传入远程服务端地址和绑定地址
/// </summary>
/// <param name="config">配置</param>
/// <param name="remoteUrl">远端IP端口配置</param>
/// <param name="bindUrl">本地IP端口配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static async ValueTask<TcpClientChannel> GetTcpClientWithIPHostAsync(this TouchSocketConfig config, string remoteUrl, string? bindUrl = default)
{
remoteUrl.ThrowIfNull(nameof(IPHost));
config.SetRemoteIPHost(remoteUrl);
if (!string.IsNullOrEmpty(bindUrl))
config.SetBindIPHost(bindUrl);
//载入配置
TcpClientChannel tcpClientChannel = new TcpClientChannel();
await tcpClientChannel.SetupAsync(config).ConfigureAwait(false);
return tcpClientChannel;
}
/// <summary>
/// 获取一个新的Tcp服务会话通道。传入远程服务端地址和绑定地址
/// </summary>
/// <param name="config">配置</param>
/// <param name="bindUrl">本地IP端口配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static async ValueTask<TcpServiceChannel> GetTcpServiceWithBindIPHostAsync(this TouchSocketConfig config, string bindUrl)
{
bindUrl.ThrowIfNull(nameof(IPHost));
var urls = bindUrl.SplitStringBySemicolon();
config.SetListenIPHosts(IPHost.ParseIPHosts(urls));
//载入配置
TcpServiceChannel tcpServiceChannel = new TcpServiceChannel();
await tcpServiceChannel.SetupAsync(config).ConfigureAwait(false);
return tcpServiceChannel;
}
/// <summary>
/// 获取一个新的Udp会话通道。传入远程服务端地址和绑定地址
/// </summary>
/// <param name="config">配置</param>
/// <param name="remoteUrl">远端IP端口配置</param>
/// <param name="bindUrl">本地IP端口配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static async ValueTask<UdpSessionChannel> GetUdpSessionWithIPHostAsync(this TouchSocketConfig config, string? remoteUrl, string? bindUrl)
{
if (string.IsNullOrEmpty(remoteUrl) && string.IsNullOrEmpty(bindUrl))
throw new ArgumentNullException(nameof(IPHost));
if (!string.IsNullOrEmpty(remoteUrl))
config.SetRemoteIPHost(remoteUrl);
if (!string.IsNullOrEmpty(bindUrl))
config.SetBindIPHost(bindUrl);
else
config.SetBindIPHost(new IPHost(0));
//载入配置
UdpSessionChannel udpSessionChannel = new UdpSessionChannel();
#if NET6_0_OR_GREATER
if (OperatingSystem.IsWindows())
{
config.UseUdpConnReset();
}
#endif
await udpSessionChannel.SetupAsync(config).ConfigureAwait(false);
return udpSessionChannel;
}
}

View File

@@ -0,0 +1,23 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 能对适配器做配置的客户端
/// </summary>
public interface IAdapterObject
{
/// <summary>
/// 设置数据处理适配器
/// </summary>
/// <param name="adapter">适配器</param>
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
}

View File

@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 通道管理
/// </summary>
public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient
{
/// <summary>
/// 接收数据事件
/// </summary>
public ChannelReceivedEventHandler ChannelReceived { get; set; }
/// <summary>
/// 通道类型
/// </summary>
public ChannelTypeEnum ChannelType { get; }
/// <summary>
/// 通道下的所有设备
/// </summary>
public ConcurrentList<IProtocol> Collects { get; }
/// <summary>
/// Online
/// </summary>
public bool Online { get; }
/// <summary>
/// MaxSign
/// </summary>
public int MaxSign { get; set; }
/// <summary>
/// 通道启动成功后
/// </summary>
public ChannelEventHandler Started { get; set; }
/// <summary>
/// 通道启动即将成功
/// </summary>
public ChannelEventHandler Starting { get; set; }
/// <summary>
/// 通道停止
/// </summary>
public ChannelEventHandler Stoped { get; set; }
/// <summary>
/// 通道停止前
/// </summary>
public ChannelEventHandler Stoping { get; set; }
/// <summary>
/// 关闭客户端。
/// </summary>
/// <param name="msg">关闭消息</param>
public void Close(string msg);
/// <summary>
/// 启动
/// </summary>
/// <param name="millisecondsTimeout">最大等待时间</param>
/// <param name="token">可取消令箭</param>
/// <exception cref="TimeoutException"></exception>
/// <exception cref="Exception"></exception>
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default);
/// <summary>
/// 异步连接
/// </summary>
/// <param name="millisecondsTimeout">最大等待时间</param>
/// <param name="token">可取消令箭</param>
/// <exception cref="TimeoutException"></exception>
/// <exception cref="Exception"></exception>
public Task ConnectAsync(int millisecondsTimeout = 3000, CancellationToken token = default);
}
/// <summary>
/// 接收事件回调类
/// </summary>
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, Task>>
{
}
/// <summary>
/// 通道事件回调类
/// </summary>
public class ChannelEventHandler : List<Func<IClientChannel, Task<bool>>>
{
}

View File

@@ -8,18 +8,26 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Foundation;
/// <summary>
/// 关系表种子数据
/// 终端通道
/// </summary>
public class SysRelationSeedData : ISqlSugarEntitySeedData<SysRelation>
public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOnlineClient, IAdapterObject
{
/// <inheritdoc/>
public IEnumerable<SysRelation> SeedData()
{
var data = SeedDataUtil.GetSeedData<SysRelation>(PathExtensions.CombinePathWithOs("SeedData", "Gateway", "seed_gateway_relation.json"));
var assembly = GetType().Assembly;
return SeedDataUtil.GetSeedDataByJson<SysRelation>(SeedDataUtil.GetManifestResourceStream(assembly, "SeedData.Gateway.seed_gateway_relation.json")).Concat(data);
}
/// <summary>
/// 当前通道的数据处理适配器
/// </summary>
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
/// <summary>
/// 通道等待池
/// </summary>
WaitHandlePool<MessageBase> WaitHandlePool { get; }
/// <summary>
/// 收发等待锁,对于大部分工业主从协议是必须的,一个通道一个实现
/// </summary>
WaitLock WaitLock { get; }
}

View File

@@ -0,0 +1,84 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Foundation.Extension.String;
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
[PluginOption(Singleton = true)]
public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
/// <summary>
/// 心跳16进制字符串
/// </summary>
public string HeartbeatHexString { get; set; }
/// <inheritdoc/>
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
{
var len = HeartbeatHexString.HexStringToBytes().Length;
if (client is TcpSessionClientChannel socket && socket.Service is TcpServiceChannel tcpServiceChannel)
{
if (!socket.Id.StartsWith("ID="))
{
var id = $"ID={e.ByteBlock}";
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
{
try
{
oldClient.TryShutdown();
await oldClient.CloseAsync().ConfigureAwait(false);
oldClient.Dispose();
}
catch
{
}
}
await socket.ResetIdAsync(id).ConfigureAwait(false);
client.Logger?.Info(DefaultResource.Localizer["DtuConnected", id]);
e.Handled = true;
}
if (!socket.Service.ClientExists(socket.Id))
{
try
{
socket.TryShutdown();
await socket.CloseAsync().ConfigureAwait(false);
socket.Dispose();
}
catch
{
}
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
return;
}
if (len > 0)
{
if (HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
{
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}
//回应心跳包
await socket.SendAsync(e.ByteBlock.AsSegment()).ConfigureAwait(false);
e.Handled = true;
if (socket.Logger?.LogLevel <= LogLevel.Trace)
socket.Logger?.Trace($"{socket}- Heartbeat");
}
}
}
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}

View File

@@ -0,0 +1,93 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Foundation.Extension.String;
namespace ThingsGateway.Foundation;
[PluginOption(Singleton = true)]
internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin
{
public string DtuId { get; set; } = "DtuId";
public string HeartbeatHexString { get; set; } = "HeartbeatHexString";
public int HeartbeatTime { get; set; } = 3;
public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e)
{
if (client is ITcpSessionClient)
{
return;//此处可判断,如果为服务器,则不用使用心跳。
}
if (DtuId.IsNullOrWhiteSpace()) return;
if (client is ITcpClient tcpClient)
{
await tcpClient.SendAsync(DtuId.ToUTF8Bytes()).ConfigureAwait(false);
_ = Task.Run(async () =>
{
var failedCount = 0;
while (true)
{
await Task.Delay(HeartbeatTime * 1000).ConfigureAwait(false);
if (!client.Online)
{
return;
}
try
{
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
}
await tcpClient.SendAsync(HeartbeatHexString.HexStringToBytes()).ConfigureAwait(false);
failedCount = 0;
}
catch
{
failedCount++;
}
if (failedCount > 3)
{
await client.CloseAsync("The automatic heartbeat has failed more than 3 times and has been disconnected.").ConfigureAwait(false);
}
}
});
}
await e.InvokeNext().ConfigureAwait(false);
}
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
{
if (client is ITcpSessionClient)
{
return;//此处可判断,如果为服务器,则不用使用心跳。
}
if (DtuId.IsNullOrWhiteSpace()) return;
if (client is ITcpClient tcpClient)
{
var len = HeartbeatHexString.HexStringToBytes().Length;
if (len > 0)
{
if (HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
{
e.Handled = true;
}
}
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}
}

View File

@@ -0,0 +1,25 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
public interface IDtu : ITcpService
{
/// <summary>
/// 心跳检测(大写16进制字符串)
/// </summary>
public string HeartbeatHexString { get; set; }
/// <summary>
/// 默认Dtu注册包,utf-8字符串
/// </summary>
public string DtuId { get; set; }
}

View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
public interface IDtuClient
{
/// <summary>
/// DtuId
/// </summary>
public string DtuId { get; set; }
/// <summary>
/// 心跳内容
/// </summary>
public string HeartbeatHexString { get; set; }
/// <summary>
/// 心跳时间
/// </summary>
public int HeartbeatTime { get; set; }
}

View File

@@ -0,0 +1,20 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
public interface ITcpService
{
/// <summary>
/// 客户端连接滑动过期时间(TCP服务通道时)
/// </summary>
public int CheckClearTime { get; set; }
}

View File

@@ -0,0 +1,75 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
public static class PluginUtil
{
/// <inheritdoc/>
public static Action<IPluginManager> GetDtuClientPlugin(IDtuClient dtuClient)
{
Action<IPluginManager> action = a => { };
action += a =>
{
var plugin = a.Add<HeartbeatAndReceivePlugin>();
plugin.HeartbeatHexString = dtuClient.HeartbeatHexString;
plugin.DtuId = dtuClient.DtuId;
plugin.HeartbeatTime = dtuClient.HeartbeatTime;
};
return action;
}
/// <inheritdoc/>
public static Action<IPluginManager> GetDtuPlugin(IDtu dtu)
{
Action<IPluginManager> action = a => { };
action += a =>
{
a.UseCheckClear()
.SetCheckClearType(CheckClearType.All)
.SetTick(TimeSpan.FromSeconds(dtu.CheckClearTime))
.SetOnClose((c, t) =>
{
c.TryShutdown();
c.SafeClose($"{dtu.CheckClearTime}s Timeout");
});
};
action += a =>
{
var plugin = a.Add<DtuPlugin>();
plugin.HeartbeatHexString = dtu.HeartbeatHexString;
};
return action;
}
/// <inheritdoc/>
public static Action<IPluginManager> GetTcpServicePlugin(ITcpService tcpService)
{
Action<IPluginManager> action = a => { };
action += a =>
{
a.UseCheckClear()
.SetCheckClearType(CheckClearType.All)
.SetTick(TimeSpan.FromSeconds(tcpService.CheckClearTime))
.SetOnClose((c, t) =>
{
c.TryShutdown();
c.SafeClose($"{tcpService.CheckClearTime}s Timeout");
});
};
return action;
}
}

View File

@@ -0,0 +1,151 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using TouchSocket.SerialPorts;
namespace ThingsGateway.Foundation;
/// <summary>
/// 串口通道
/// </summary>
public class SerialPortChannel : SerialPortClient, IClientChannel
{
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
/// <inheritdoc/>
public SerialPortChannel()
{
WaitHandlePool.MaxSign = ushort.MaxValue;
}
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
/// <inheritdoc/>
public ChannelTypeEnum ChannelType => ChannelTypeEnum.SerialPort;
/// <inheritdoc/>
public ConcurrentList<IProtocol> Collects { get; } = new();
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
/// <inheritdoc/>
public WaitLock WaitLock { get; } = new WaitLock();
/// <inheritdoc/>
public void Close(string msg)
{
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
{
if (Online)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.CloseAsync(msg).ConfigureAwait(false);
Logger?.Debug($"{ToString()} Closed{msg}");
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
{
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public new async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
{
if (!Online)
{
try
{
await m_semaphoreForConnect.WaitAsync(token).ConfigureAwait(false);
if (!Online)
{
await SetupAsync(Config.Clone()).ConfigureAwait(false);
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
Logger?.Debug($"{ToString()} Connected");
await this.OnChannelEvent(Started).ConfigureAwait(false);
}
}
finally
{
m_semaphoreForConnect.Release();
}
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public override string? ToString()
{
var port = Config?.GetValue(SerialPortConfigExtension.SerialPortOptionProperty);
if (port != null)
return $"{port.PortName}[{port.BaudRate},{port.DataBits},{port.StopBits},{port.Parity}]";
return base.ToString();
}
/// <inheritdoc/>
protected override async Task OnSerialClosing(ClosingEventArgs e)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnSerialClosing(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
{
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnSerialConnecting(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
{
await base.OnSerialReceived(e).ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,143 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// Tcp客户端通道
/// </summary>
public class TcpClientChannel : TcpClient, IClientChannel
{
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
/// <inheritdoc/>
public TcpClientChannel()
{
WaitHandlePool.MaxSign = ushort.MaxValue;
}
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
/// <inheritdoc/>
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpClient;
/// <inheritdoc/>
public ConcurrentList<IProtocol> Collects { get; } = new();
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
/// <inheritdoc/>
public WaitLock WaitLock { get; } = new WaitLock();
/// <inheritdoc/>
public void Close(string msg)
{
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public override async Task CloseAsync(string msg)
{
if (Online)
{
await base.CloseAsync(msg).ConfigureAwait(false);
Logger?.Debug($"{ToString()} Closed{msg}");
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
{
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
{
if (!Online)
{
try
{
await m_semaphoreForConnect.WaitAsync(token).ConfigureAwait(false);
if (!Online)
{
await SetupAsync(Config.Clone()).ConfigureAwait(false);
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
Logger?.Debug($"{ToString()} Connected");
await this.OnChannelEvent(Started).ConfigureAwait(false);
}
}
finally
{
m_semaphoreForConnect.Release();
}
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public override string ToString()
{
return $"{IP}:{Port}";
}
/// <inheritdoc/>
protected override async Task OnTcpClosing(ClosingEventArgs e)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnTcpClosing(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
{
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnTcpConnecting(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
await base.OnTcpReceived(e).ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,231 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// TCP服务器
/// </summary>
/// <typeparam name="TClient"></typeparam>
public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcpService<TClient> where TClient : TcpSessionClientChannel, new()
{
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
/// <inheritdoc/>
public ConcurrentList<IProtocol> Collects { get; } = new();
/// <summary>
/// 停止时是否发送ShutDown
/// </summary>
public bool ShutDownEnable { get; set; } = true;
/// <inheritdoc/>
public override async Task ClearAsync()
{
foreach (var client in Clients)
{
try
{
if (ShutDownEnable)
client.TryShutdown();
await client.CloseAsync().ConfigureAwait(false);
client.SafeDispose();
}
catch
{
}
}
}
public async Task ClientDisposeAsync(string id)
{
if (this.TryGetClient(id, out var client))
{
if (ShutDownEnable)
client.TryShutdown();
await client.CloseAsync().ConfigureAwait(false);
client.SafeDispose();
}
}
/// <inheritdoc/>
public override async Task StartAsync()
{
try
{
await m_semaphoreForConnect.WaitAsync().ConfigureAwait(false);
if (ServerState != ServerState.Running)
{
await base.StopAsync().ConfigureAwait(false);
await SetupAsync(Config.Clone()).ConfigureAwait(false);
await base.StartAsync().ConfigureAwait(false);
if (ServerState == ServerState.Running)
{
Logger?.Info($"{Monitors.FirstOrDefault()?.Option.IpHost}{DefaultResource.Localizer["ServiceStarted"]}");
}
}
else
{
await base.StartAsync().ConfigureAwait(false);
}
}
finally
{
m_semaphoreForConnect.Release();
}
}
/// <inheritdoc/>
public override async Task StopAsync()
{
if (Monitors.Any())
{
await ClearAsync().ConfigureAwait(false);
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
await base.StopAsync().ConfigureAwait(false);
if (!Monitors.Any())
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
}
else
{
await base.StopAsync().ConfigureAwait(false);
}
}
/// <inheritdoc/>
public override string? ToString()
{
return Monitors.FirstOrDefault()?.Option?.IpHost.ToString();
}
/// <inheritdoc/>
protected override Task OnTcpClosed(TClient socketClient, ClosedEventArgs e)
{
Logger?.Debug($"{socketClient} Closed");
return base.OnTcpClosed(socketClient, e);
}
/// <inheritdoc/>
protected override Task OnTcpClosing(TClient socketClient, ClosingEventArgs e)
{
Logger?.Debug($"{socketClient} Closing");
return base.OnTcpClosing(socketClient, e);
}
/// <inheritdoc/>
protected override Task OnTcpConnected(TClient socketClient, ConnectedEventArgs e)
{
Logger?.Debug($"{socketClient} Connected");
return base.OnTcpConnected(socketClient, e);
}
/// <inheritdoc/>
protected override Task OnTcpConnecting(TClient socketClient, ConnectingEventArgs e)
{
Logger?.Debug($"{socketClient} Connecting");
return base.OnTcpConnecting(socketClient, e);
}
}
/// <summary>
/// Tcp服务器通道
/// </summary>
public class TcpServiceChannel : TcpServiceChannelBase<TcpSessionClientChannel>, IChannel
{
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
/// <inheritdoc/>
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpService;
/// <inheritdoc/>
public bool Online => ServerState == ServerState.Running;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
/// <inheritdoc/>
public void Close(string msg)
{
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public Task CloseAsync(string msg)
{
return StopAsync();
}
/// <inheritdoc/>
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
{
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
{
if (token.IsCancellationRequested)
return EasyTask.CompletedTask;
return StartAsync();
}
/// <inheritdoc/>
protected override TcpSessionClientChannel NewClient()
{
var data = new TcpSessionClientChannel();
data.WaitHandlePool.MaxSign = MaxSign;
return data;
}
public int MaxSign { get; set; }
/// <inheritdoc/>
protected override async Task OnTcpClosing(TcpSessionClientChannel socketClient, ClosingEventArgs e)
{
await socketClient.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.OnTcpClosing(socketClient, e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpClosed(TcpSessionClientChannel socketClient, ClosedEventArgs e)
{
await socketClient.OnChannelEvent(Stoped).ConfigureAwait(false);
await base.OnTcpClosed(socketClient, e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpConnected(TcpSessionClientChannel socketClient, ConnectedEventArgs e)
{
await socketClient.OnChannelEvent(Started).ConfigureAwait(false);
await base.OnTcpConnected(socketClient, e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpConnecting(TcpSessionClientChannel socketClient, ConnectingEventArgs e)
{
await socketClient.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnTcpConnecting(socketClient, e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpReceived(TcpSessionClientChannel socketClient, ReceivedDataEventArgs e)
{
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,147 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// Tcp终端通道
/// </summary>
public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
{
/// <inheritdoc/>
public TcpSessionClientChannel()
{
WaitHandlePool.MaxSign = ushort.MaxValue;
}
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
/// <inheritdoc/>
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpService;
/// <inheritdoc/>
public ConcurrentList<IProtocol> Collects { get; } = new();
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
/// <inheritdoc/>
public WaitLock WaitLock { get; } = new WaitLock();
/// <inheritdoc/>
public void Close(string msg)
{
CloseAsync(msg).ConfigureAwait(false);
}
/// <inheritdoc/>
public override Task CloseAsync(string msg)
{
WaitHandlePool.SafeDispose();
return base.CloseAsync(msg);
}
/// <inheritdoc/>
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default) => throw new NotSupportedException();
/// <inheritdoc/>
public Task ConnectAsync(int timeout, CancellationToken token) => throw new NotImplementedException();
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public Task SetupAsync(TouchSocketConfig config)
{
return EasyTask.CompletedTask;
}
public override async Task ResetIdAsync(string newId)
{
await base.ResetIdAsync(newId).ConfigureAwait(false);
}
/// <inheritdoc/>
public override string ToString()
{
return $"{IP}:{Port}:{Id}";
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (DisposedValue) return;
WaitHandlePool.SafeDispose();
base.Dispose(disposing);
}
/// <inheritdoc/>
protected override async Task OnTcpClosed(ClosedEventArgs e)
{
Logger?.Debug($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
await base.OnTcpClosed(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpClosing(ClosingEventArgs e)
{
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.OnTcpClosing(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpConnected(ConnectedEventArgs e)
{
//Logger?.Debug($"{ToString()}{FoundationConst.Connected}");
await this.OnChannelEvent(Started).ConfigureAwait(false);
await base.OnTcpConnected(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
{
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnTcpConnecting(e).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
await base.OnTcpReceived(e).ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,158 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// Udp通道
/// </summary>
public class UdpSessionChannel : UdpSession, IClientChannel
{
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
/// <inheritdoc/>
public UdpSessionChannel()
{
WaitHandlePool.MaxSign = ushort.MaxValue;
}
public int MaxSign { get => WaitHandlePool.MaxSign; set => WaitHandlePool.MaxSign = value; }
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
/// <inheritdoc/>
public ChannelTypeEnum ChannelType => ChannelTypeEnum.UdpSession;
/// <inheritdoc/>
public ConcurrentList<IProtocol> Collects { get; } = new();
/// <inheritdoc/>
public bool Online => ServerState == ServerState.Running;
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
/// <inheritdoc/>
public ChannelEventHandler Started { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Starting { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoped { get; set; } = new();
/// <inheritdoc/>
public ChannelEventHandler Stoping { get; set; } = new();
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new();
/// <inheritdoc/>
public WaitLock WaitLock { get; } = new WaitLock();
/// <inheritdoc/>
public void Close(string msg)
{
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public Task CloseAsync(string msg)
{
return StopAsync();
}
/// <inheritdoc/>
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
{
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public async Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
{
if (token.IsCancellationRequested)
return;
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await StartAsync().ConfigureAwait(false);
await this.OnChannelEvent(Started).ConfigureAwait(false);
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
}
/// <inheritdoc/>
public override async Task StartAsync()
{
try
{
await m_semaphoreForConnect.WaitAsync().ConfigureAwait(false);
if (ServerState != ServerState.Running)
{
await base.StopAsync().ConfigureAwait(false);
await SetupAsync(Config.Clone()).ConfigureAwait(false);
await base.StartAsync().ConfigureAwait(false);
if (ServerState == ServerState.Running)
{
Logger?.Info($"{Monitor.IPHost}{DefaultResource.Localizer["ServiceStarted"]}");
}
}
else
{
await base.StartAsync().ConfigureAwait(false);
}
}
finally
{
m_semaphoreForConnect.Release();
}
}
/// <inheritdoc/>
public override async Task StopAsync()
{
if (Monitor != null)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
await base.StopAsync().ConfigureAwait(false);
if (Monitor == null)
{
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
}
}
else
{
await base.StopAsync().ConfigureAwait(false);
}
}
/// <inheritdoc/>
public override string? ToString()
{
return RemoteIPHost?.ToString().Replace("tcp", "udp");
}
/// <inheritdoc/>
protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e)
{
await base.OnUdpReceived(e).ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,127 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 自增数据类,用于自增数据,可以设置最大值,初始值,自增步长等。
/// </summary>
public sealed class IncrementCount : DisposableObject
{
private readonly WaitLock easyLock = new();
private long current = 0;
private long max = long.MaxValue;
private long start = 0;
/// <inheritdoc cref="IncrementCount"/>
public IncrementCount(long max, long start = 0, int tick = 1)
{
this.start = start;
this.max = max;
current = start;
IncreaseTick = tick;
}
/// <summary>
/// 自增步长
/// </summary>
public int IncreaseTick { get; set; } = 1;
/// <summary>
/// 获取当前的计数器的最大的设置值
/// </summary>
public long MaxValue => max;
/// <summary>
/// 获取自增信息,获得数据之后,下一次获取将会自增,如果自增后大于最大值,则会重置为最小值,如果小于最小值,则会重置为最大值。
/// </summary>
public long GetCurrentValue()
{
easyLock.Wait();
long current = this.current;
this.current += IncreaseTick;
if (this.current > max)
{
this.current = start;
}
else if (this.current < start)
{
this.current = max;
}
easyLock.Release();
return current;
}
/// <summary>
/// 将当前的值重置为初始值。
/// </summary>
public void ResetCurrentValue()
{
easyLock.Wait();
current = start;
easyLock.Release();
}
/// <summary>
/// 将当前的值重置为指定值
/// </summary>
/// <param name="value">指定值</param>
public void ResetCurrentValue(long value)
{
easyLock.Wait();
current = value <= max ? value >= start ? value : start : max;
easyLock.Release();
}
/// <summary>
/// 重置当前序号的最大值
/// </summary>
public void ResetMaxValue(long max)
{
easyLock.Wait();
if (max > start)
{
if (max < current)
{
current = start;
}
this.max = max;
}
easyLock.Release();
}
/// <summary>
/// 重置当前序号的初始值
/// </summary>
/// <param name="start">初始值</param>
public void ResetStartValue(long start)
{
easyLock.Wait();
if (start < max)
{
if (current < start)
{
current = start;
}
this.start = start;
}
easyLock.Release();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
easyLock.SafeDispose();
base.Dispose(disposing);
}
}

View File

@@ -0,0 +1,87 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.NewLife.Threading;
namespace ThingsGateway.Foundation;
/// <summary>
/// 时间刻度器,最小时间间隔10毫秒
/// </summary>
public class TimeTick
{
/// <summary>
/// 时间间隔(毫秒)
/// </summary>
private readonly int intervalMilliseconds = 1000;
private readonly Cron cron;
/// <inheritdoc cref="TimeTick"/>
public TimeTick(string delay)
{
if (int.TryParse(delay, out intervalMilliseconds))
{
if (intervalMilliseconds < 10)
intervalMilliseconds = 10;
LastTime = DateTime.Now.AddMilliseconds(-intervalMilliseconds);
}
else
{
cron = new Cron(delay);
}
}
/// <summary>
/// 上次触发时间
/// </summary>
public DateTime LastTime { get; private set; } = DateTime.Now;
/// <summary>
/// 是否触发时间刻度
/// </summary>
/// <param name="currentTime">当前时间</param>
/// <returns>是否触发时间刻度</returns>
public bool IsTickHappen(DateTime currentTime)
{
DateTime nextTime = DateTime.MinValue;
if (cron == null)
{
nextTime = LastTime.AddMilliseconds(intervalMilliseconds);
var diffMilliseconds = (currentTime - nextTime).TotalMilliseconds;
if (diffMilliseconds < 0)
return false;
else if (diffMilliseconds * 2 < intervalMilliseconds)
LastTime = nextTime;
else
LastTime = nextTime;//选择当前时间
return true;
}
else
{
nextTime = cron.GetNext(LastTime);
if (currentTime >= nextTime)
{
LastTime = nextTime;
return true;
}
else
{
return false;
}
}
}
/// <summary>
/// 是否到达设置的时间间隔
/// </summary>
/// <returns>是否到达设置的时间间隔</returns>
public bool IsTickHappen() => IsTickHappen(DateTime.Now);
}

View File

@@ -0,0 +1,89 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// WaitLock使用轻量级SemaphoreSlim锁只允许一个并发量并记录并发信息
/// </summary>
public sealed class WaitLock : DisposableObject
{
private readonly SemaphoreSlim m_waiterLock = new SemaphoreSlim(1, 1);
/// <inheritdoc/>
public WaitLock(bool initialState = true)
{
if (!initialState)
m_waiterLock.Wait();
}
/// <inheritdoc/>
~WaitLock()
{
this.SafeDispose();
}
/// <summary>
/// 当前锁是否在等待当中
/// </summary>
public bool IsWaitting => m_waiterLock.CurrentCount == 0;
/// <summary>
/// 离开锁
/// </summary>
public void Release()
{
lock (this)
{
if (IsWaitting)
m_waiterLock.Release();
}
}
/// <summary>
/// 进入锁
/// </summary>
public void Wait(CancellationToken cancellationToken = default)
{
m_waiterLock.Wait(cancellationToken);
}
/// <summary>
/// 进入锁
/// </summary>
public bool Wait(TimeSpan timeSpan, CancellationToken cancellationToken = default)
{
var data = m_waiterLock.Wait(timeSpan, cancellationToken);
return data;
}
/// <summary>
/// 进入锁
/// </summary>
public Task WaitAsync(CancellationToken cancellationToken = default)
{
return m_waiterLock.WaitAsync(cancellationToken);
}
/// <summary>
/// 进入锁
/// </summary>
public Task<bool> WaitAsync(TimeSpan timeSpan, CancellationToken cancellationToken = default)
{
return m_waiterLock.WaitAsync(timeSpan, cancellationToken);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
m_waiterLock.SafeDispose();
base.Dispose(disposing);
}
}

View File

@@ -0,0 +1,59 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Newtonsoft.Json;
namespace ThingsGateway.Foundation;
/// <summary>
/// Json字符串转到对应类
/// </summary>
public class JsonStringToClassSerializerFormatter<TState> : ISerializerFormatter<string, TState>
{
/// <summary>
/// JsonSettings
/// </summary>
public JsonSerializerSettings JsonSettings { get; set; } = new JsonSerializerSettings();
/// <summary>
/// <inheritdoc/>
/// </summary>
public int Order { get; set; } = -99;
/// <inheritdoc/>
public bool TryDeserialize(TState state, in string source, Type targetType, out object target)
{
try
{
target = JsonConvert.DeserializeObject(source, targetType, JsonSettings)!;
return true;
}
catch
{
target = default;
return false;
}
}
/// <inheritdoc/>
public bool TrySerialize(TState state, in object target, out string source)
{
try
{
source = JsonConvert.SerializeObject(target, JsonSettings);
return true;
}
catch (Exception)
{
source = null;
return false;
}
}
}

View File

@@ -0,0 +1,45 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.Foundation.Extension.String;
namespace ThingsGateway.Foundation;
/// <summary>
/// String值转换为基础类型。
/// </summary>
public class StringToClassConverter<TState> : ISerializerFormatter<string, TState>
{
/// <summary>
/// <inheritdoc/>
/// </summary>
public int Order { get; set; } = -100;
/// <inheritdoc/>
public bool TryDeserialize(TState state, in string source, Type targetType, out object target)
{
return targetType.GetTypeValue(source, out target!);
}
/// <inheritdoc/>
public bool TrySerialize(TState state, in object target, out string source)
{
if (target != null)
{
var targetType = target.GetType();
return targetType.GetTypeStringValue(target, out source!);
}
else
{
source = null;
return true;
}
}
}

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using System.Text;
namespace ThingsGateway.Foundation;
/// <inheritdoc/>
public class StringToEncodingConverter : ISerializerFormatter<string, object>
{
/// <inheritdoc/>
public int Order { get; set; }
/// <inheritdoc/>
public bool TryDeserialize(object state, in string source, Type targetType, out object target)
{
try
{
target = Encoding.Default;
if (targetType == typeof(Encoding))
{
target = Encoding.GetEncoding(source);
return true;
}
}
catch
{
target = default;
return false;
}
return false;
}
/// <inheritdoc/>
public bool TrySerialize(object state, in object target, out string source)
{
try
{
if (target?.GetType() == typeof(Encoding))
{
source = (target as Encoding).WebName;
return true;
}
source = target.ToJsonString();
return true;
}
catch (Exception)
{
source = null;
return false;
}
}
}

View File

@@ -0,0 +1,31 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// String类型数据转换器
/// </summary>
public class ThingsGatewayStringConverter : StringSerializerConverter
{
/// <summary>
/// 默认实例
/// </summary>
public static ThingsGatewayStringConverter Default = new ThingsGatewayStringConverter();
/// <summary>
/// 构造函数
/// </summary>
public ThingsGatewayStringConverter(params ISerializerFormatter<string, object>[] converters) : base(converters)
{
Add(new StringToClassConverter<object>());
Add(new JsonStringToClassSerializerFormatter<object>());
}
}

View File

@@ -0,0 +1,58 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 采集返回消息
/// </summary>
public interface IResultMessage : IOperResult, IRequestInfo
{
/// <summary>
/// 数据体长度
/// </summary>
int BodyLength { get; set; }
/// <summary>
/// 解析的字节信息
/// </summary>
byte[] Content { get; set; }
/// <summary>
/// 消息头的指令长度,不固定时返回0
/// </summary>
int HeaderLength { get; }
/// <summary>
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
/// </summary>
int Sign { get; set; }
/// <summary>
/// 当收到数据,由框架封送有效载荷数据。
/// 此时流位置为<see cref="HeaderLength"/>
/// <para>但是如果是因为数据错误,则需要修改<see cref="ByteBlock.Position"/>到正确位置,如果都不正确,则设置<see cref="ByteBlock.Position"/>等于<see cref="ByteBlock.Length"/></para>
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
/// </summary>
/// <returns>是否成功有效</returns>
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
/// <summary>
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
/// <para>如果返回false意味着放弃本次解析的所有数据包括已经解析完成的Header</para>
/// </summary>
/// <returns>是否成功的结果</returns>
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
/// <summary>
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
/// </summary>
void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock);
}

View File

@@ -0,0 +1,18 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 发送消息
/// </summary>
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
{
}

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