diff --git a/1257-18B.sln b/1257-18B.sln
new file mode 100644
index 0000000..33d022e
--- /dev/null
+++ b/1257-18B.sln
@@ -0,0 +1,141 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33530.505
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.Helper", "JinYuan.Helper\JinYuan.Helper.csproj", "{258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.VirtualDataLibrary", "JinYuan.VirtualDataLibrary\JinYuan.VirtualDataLibrary.csproj", "{2AB7E380-7795-448A-933C-0F6A081B5E7E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.Models", "JinYuan.Models\JinYuan.Models.csproj", "{4C0BCB84-5CD7-4846-8363-C4C10D46E391}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.ControlCenters", "JinYuan.ControlCenter\JinYuan.ControlCenters.csproj", "{1D0FA5A4-07F0-4621-B6BB-683AEC91E658}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.DAL", "JinYuan.DAL\JinYuan.DAL.csproj", "{114AA012-29C9-418C-A2D6-3173C5341F51}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.MES", "JinYuan.MES\JinYuan.MES.csproj", "{530B6426-08E7-461C-91DC-787F876620FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LargeSquareOne", "LargeSquareOne\LargeSquareOne.csproj", "{29E1F0D0-43DC-4077-A136-F25477E56881}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PLC", "PLC\PLC.csproj", "{486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JinYuan.ControlLib", "JinYuan.ControlLib\JinYuan.ControlLib.csproj", "{1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Language", "Language\Language.csproj", "{292A5C9C-7A82-4D25-A885-DDCEDC93F03C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOT.Services", "IOT.Services\IOT.Services.csproj", "{242400F4-9B01-4AEE-A6F7-05083757FCDD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{9FA3D6BD-1EC1-3BA5-80CB-CE02773A58D5}"
+ ProjectSection(SolutionItems) = preProject
+ 数据库\gluingmachineoftabflatteningandwelding_a.sql = 数据库\gluingmachineoftabflatteningandwelding_a.sql
+ 数据库\hbg_cupdataturnover.sql = 数据库\hbg_cupdataturnover.sql
+ TextFile1.txt = TextFile1.txt
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Debug|x64.Build.0 = Debug|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Release|Any CPU.Build.0 = Release|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Release|x64.ActiveCfg = Release|Any CPU
+ {258D0AB7-2B4F-4D8F-A3CD-7A8CB4A85360}.Release|x64.Build.0 = Release|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Debug|x64.Build.0 = Debug|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Release|x64.ActiveCfg = Release|Any CPU
+ {2AB7E380-7795-448A-933C-0F6A081B5E7E}.Release|x64.Build.0 = Release|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Debug|x64.Build.0 = Debug|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Release|x64.ActiveCfg = Release|Any CPU
+ {4C0BCB84-5CD7-4846-8363-C4C10D46E391}.Release|x64.Build.0 = Release|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Debug|x64.Build.0 = Debug|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Release|x64.ActiveCfg = Release|Any CPU
+ {1D0FA5A4-07F0-4621-B6BB-683AEC91E658}.Release|x64.Build.0 = Release|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Debug|x64.Build.0 = Debug|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Release|Any CPU.Build.0 = Release|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Release|x64.ActiveCfg = Release|Any CPU
+ {114AA012-29C9-418C-A2D6-3173C5341F51}.Release|x64.Build.0 = Release|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Debug|x64.Build.0 = Debug|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Release|x64.ActiveCfg = Release|Any CPU
+ {530B6426-08E7-461C-91DC-787F876620FC}.Release|x64.Build.0 = Release|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Debug|x64.Build.0 = Debug|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Release|Any CPU.Build.0 = Release|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Release|x64.ActiveCfg = Release|Any CPU
+ {29E1F0D0-43DC-4077-A136-F25477E56881}.Release|x64.Build.0 = Release|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Debug|x64.Build.0 = Debug|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Release|x64.ActiveCfg = Release|Any CPU
+ {486AECD0-C2BF-43C5-B7D7-88E565ADE4B7}.Release|x64.Build.0 = Release|Any CPU
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Debug|x64.ActiveCfg = Debug|x64
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Debug|x64.Build.0 = Debug|x64
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Release|x64.ActiveCfg = Release|x64
+ {1BB7ED33-D2CB-4CDD-AB39-F7F9AE948CA2}.Release|x64.Build.0 = Release|x64
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Debug|x64.Build.0 = Debug|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Release|x64.ActiveCfg = Release|Any CPU
+ {292A5C9C-7A82-4D25-A885-DDCEDC93F03C}.Release|x64.Build.0 = Release|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Debug|x64.Build.0 = Debug|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Release|x64.ActiveCfg = Release|Any CPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}.Release|x64.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E8421996-BAC4-4509-9C3E-BC424197F2D8}
+ RESX_NeutralResourcesLanguage = zh-Hans
+ RESX_SortFileContentOnSave = True
+ RESX_AutoCreateNewLanguageFiles = False
+ EndGlobalSection
+EndGlobal
diff --git a/BTW/新栈板V1.0.btw b/BTW/新栈板V1.0.btw
new file mode 100644
index 0000000..3adb7ec
Binary files /dev/null and b/BTW/新栈板V1.0.btw differ
diff --git a/BTW/新箱唛V1.0.btw b/BTW/新箱唛V1.0.btw
new file mode 100644
index 0000000..8c9ac56
Binary files /dev/null and b/BTW/新箱唛V1.0.btw differ
diff --git a/IOT.Services/App.config b/IOT.Services/App.config
new file mode 100644
index 0000000..62f6cc9
--- /dev/null
+++ b/IOT.Services/App.config
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/IOT.Services/Config.xml b/IOT.Services/Config.xml
new file mode 100644
index 0000000..a0fbd14
--- /dev/null
+++ b/IOT.Services/Config.xml
@@ -0,0 +1,3207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/IOT.Services/IOT.Services.csproj b/IOT.Services/IOT.Services.csproj
new file mode 100644
index 0000000..6da8971
--- /dev/null
+++ b/IOT.Services/IOT.Services.csproj
@@ -0,0 +1,127 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {242400F4-9B01-4AEE-A6F7-05083757FCDD}
+ WinExe
+ IOT.Services
+ IOT.Services
+ v4.8
+ 512
+ true
+ true
+
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+ 10.0
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ Lib\DataConvertLib.dll
+
+
+ False
+ Lib\Newtonsoft.Json.dll
+
+
+ ..\packages\NLog.6.0.2\lib\net46\NLog.dll
+
+
+ False
+ Lib\PLCCommunication.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+ Service1.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+ False
+ Microsoft .NET Framework 4.8 %28x86 和 x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
\ No newline at end of file
diff --git a/IOT.Services/Lib/Newtonsoft.Json.dll b/IOT.Services/Lib/Newtonsoft.Json.dll
new file mode 100644
index 0000000..7af125a
Binary files /dev/null and b/IOT.Services/Lib/Newtonsoft.Json.dll differ
diff --git a/IOT.Services/Lib/PLCCommunication.dll b/IOT.Services/Lib/PLCCommunication.dll
new file mode 100644
index 0000000..1993b11
Binary files /dev/null and b/IOT.Services/Lib/PLCCommunication.dll differ
diff --git a/IOT.Services/Logger.cs b/IOT.Services/Logger.cs
new file mode 100644
index 0000000..4d6a8d7
--- /dev/null
+++ b/IOT.Services/Logger.cs
@@ -0,0 +1,135 @@
+using NLog;
+using System;
+using System.IO;
+using System.Timers;
+
+
+public class Logger
+{
+
+
+ private static readonly NLog.Logger loginfo = LogManager.GetLogger("loginfo");
+ private static readonly NLog.Logger logerror = LogManager.GetLogger("logerror");
+ private static readonly NLog.Logger logmqtt = LogManager.GetLogger("logmqtt");
+
+ private static readonly NLog.Logger logsInfo = LogManager.GetLogger("logsInfo");
+ private static readonly NLog.Logger logsMEStIME = LogManager.GetLogger("logsMEStIME");
+ private static readonly NLog.Logger logsSql = LogManager.GetLogger("logsSql");
+ private static readonly NLog.Logger logsSysErrorLog = LogManager.GetLogger("logsSysErrorLog");
+ private static readonly NLog.Logger logsSysErrorLogcn = LogManager.GetLogger("logsSysErrorLogcn");
+ private static readonly Timer cleanupTimer;
+ private const int MAX_DAYS = 7;
+ private const string LOG_BASE_PATH = "Log";
+
+ static Logger()
+ {
+ try
+ {
+ // 创建一个每天执行一次的定时器
+ cleanupTimer = new Timer(24 * 60 * 60 * 1000); // 24小时
+ cleanupTimer.Elapsed += CleanupOldLogs;
+ cleanupTimer.Start();
+
+ // 启动时执行一次清理
+ CleanupOldLogs(null, null);
+ }
+ catch (Exception ex)
+ {
+ WriteError("Failed to initialize logger cleanup timer", ex);
+ }
+ }
+
+ public static void WriteInfo(string info)
+ {
+ if (loginfo.IsInfoEnabled)
+ {
+ loginfo.Info(info);
+ }
+ }
+
+ public static void WriteMqtt(string info)
+ {
+ if (logmqtt.IsInfoEnabled)
+ {
+ logmqtt.Info(info);
+ }
+ }
+
+ public static void WriteError(string error)
+ {
+ if (logerror.IsErrorEnabled)
+ {
+ logerror.Error(error);
+ }
+ }
+
+ public static void WriteError(string info, Exception ex)
+ {
+ if (logerror.IsErrorEnabled)
+ {
+ logerror.Error(info, ex);
+ }
+ }
+
+ private static void CleanupOldLogs(object sender, ElapsedEventArgs e)
+ {
+ try
+ {
+ if (!Directory.Exists(LOG_BASE_PATH))
+ return;
+
+ string[] logTypes = { "LogError", "LogInfo", "LogMqtt" };
+ DateTime cutoffDate = DateTime.Now.AddDays(-MAX_DAYS);
+
+ foreach (string logType in logTypes)
+ {
+ string logTypeDir = Path.Combine(LOG_BASE_PATH, logType);
+ if (!Directory.Exists(logTypeDir))
+ continue;
+
+ // 获取所有日期文件夹
+ string[] dateDirs = Directory.GetDirectories(logTypeDir);
+ foreach (string dateDir in dateDirs)
+ {
+ string dirName = Path.GetFileName(dateDir);
+ if (DateTime.TryParseExact(dirName, "yyyyMMdd", null,
+ System.Globalization.DateTimeStyles.None, out DateTime dirDate))
+ {
+ if (dirDate < cutoffDate)
+ {
+ try
+ {
+ Directory.Delete(dateDir, true);
+ WriteInfo($"Deleted old log directory: {dateDir}");
+ }
+ catch (Exception ex)
+ {
+ WriteError($"Failed to delete log directory: {dateDir}", ex);
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteError("Error during log cleanup", ex);
+ }
+ }
+
+ // 提供手动清理方法
+ public static void ForceCleanupOldLogs()
+ {
+ CleanupOldLogs(null, null);
+ }
+
+ // 在应用程序退出时调用此方法
+ public static void Shutdown()
+ {
+ if (cleanupTimer != null)
+ {
+ cleanupTimer.Stop();
+ cleanupTimer.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/IOT.Services/Models.cs b/IOT.Services/Models.cs
new file mode 100644
index 0000000..97e069c
--- /dev/null
+++ b/IOT.Services/Models.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+
+namespace WindowsService1
+{
+ #region 1、系统内部
+ public enum AcquisitionType
+ {
+ PPM,
+ Axis,
+ Cylinder,
+ LRobot
+ }
+
+ public enum PLCDataType
+ {
+ Short,
+ Float
+ }
+
+ public class StationData
+ {
+ public string Name { get; set; }
+ public object MainValue { get; set; }
+ public DateTime Timestamp { get; set; }
+ }
+
+ public class TypeConfig
+ {
+ public string Name { get; set; }
+ public string Address { get; set; }
+ public string DataType { get; set; }
+ }
+
+
+
+ public class CollectionGroupConfig
+ {
+ public string Name { get; set; }
+ public string StartAddress { get; set; }
+ public int Length { get; set; }
+ public string DefaultDataType { get; set; }
+ public Dictionary Types { get; set; }
+ }
+ #endregion
+
+ #region 2、PPM点位
+ #endregion
+
+ #region 3、预测性点位
+ #endregion
+
+ #region 4、机械臂
+ //直角坐标机械臂
+ public class LRobot
+ {
+ public string bu_id { get; set; }
+ public string district_id { get; set; }
+ public string factory_id { get; set; }
+ public string production_line_id { get; set; }
+ public string production_processes_id { get; set; }
+ public string work_center_id { get; set; }
+ public string station_id { get; set; }
+ public string device_name { get; set; } = "601PWM01";
+ public string robot_name { get; set; }
+ public List taglist { get; set; }
+ }
+ public class Taglist
+ {
+ public string device_time { get; set; }
+ public Collection collections_items { get; set; }
+
+ }
+ public class Collection
+ {
+ public string robot_status { get; set; } = "1";
+ public string x_value { get; set; }
+ public string y_value { get; set; }
+ public string z_value { get; set; }
+ public string time_span { get; set; } = "2.5";
+ public string hand_status { get; set; } = "1";
+
+ }
+ #endregion
+}
diff --git a/IOT.Services/MqttClientWrapper.cs b/IOT.Services/MqttClientWrapper.cs
new file mode 100644
index 0000000..1e77e14
--- /dev/null
+++ b/IOT.Services/MqttClientWrapper.cs
@@ -0,0 +1,376 @@
+using Newtonsoft.Json.Linq;
+using PLCCommunication;
+using PLCCommunication.LogNet;
+using PLCCommunication.MQTT;
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace WindowsService1
+{
+ public class MqttClientWrapper
+ {
+ private MqttClient mqttClient;
+ private bool mqttIsConnected;
+ private const int MaxReconnectAttempts = 5;
+ private const int ReconnectDelay = 5000; // 5 seconds
+
+ private MqttConnectionOptions options;
+ public event Action OnMessageReceived;
+ public long ReceiveCount { get; private set; }
+
+ public PLCCommunication.LogNet.ILogNet LogNet = new LogNetDateTime("D:\\APILog\\Logs\\MQTTLogs", GenerateMode.ByEveryDay, 20);
+
+ public enum MessageFormat
+ {
+ Binary,
+ Text,
+ Xml,
+ Json
+ }
+
+
+ public MqttClientWrapper(string ipAddress, int port, string clientId, MqttCredential credentials)
+ {
+ InitializeMqttOptions(ipAddress, port, clientId, credentials);
+ }
+
+ private void InitializeMqttOptions(string ipAddress, int port, string clientId, MqttCredential credentials)
+ {
+ options = new MqttConnectionOptions
+ {
+ IpAddress = ipAddress,
+ Port = port,
+ ClientId = clientId,
+ Credentials = credentials,
+ UseRSAProvider = false,
+ CleanSession = false,
+ UseSSL = false,
+ SSLSecure = false,
+ CertificateFile = "",
+ KeepAlivePeriod = TimeSpan.FromSeconds(30),
+ KeepAliveSendInterval = TimeSpan.FromSeconds(10),
+ ConnectTimeout = 5,
+ WillMessage = null
+ };
+ }
+
+ public async Task InitializeAndConnectAsync()
+ {
+ try
+ {
+ mqttClient = new MqttClient(options);
+ Logger.WriteMqtt("MQTT 初始化成功");
+
+ await ConnectMqttAsync();
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"MQTT 初始化失败: {ex.Message}");
+ }
+ }
+
+ private async Task ConnectMqttAsync()
+ {
+ try
+ {
+ JYResult connect = await mqttClient.ConnectServerAsync();
+ if (connect.IsSuccess)
+ {
+ mqttIsConnected = true;
+ Logger.WriteMqtt("MQTT 连接服务器成功");
+ }
+ else
+ {
+ Logger.WriteError("MQTT 无法连接到服务器");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"MQTT 无法连接到服务器: {ex.Message}");
+ }
+ }
+
+
+ public async Task SendMessageAsync(string source, string publishTopic, string type, JObject messageObj)
+ {
+ if (!mqttClient.IsConnected)
+ {
+ mqttIsConnected = false;
+
+ Logger.WriteMqtt("客户端未连接到服务器。正在尝试重新连接...");
+
+ await ReconnectWithRetry();
+ if (!mqttClient.IsConnected)
+ {
+ throw new InvalidOperationException("无法与 MQTT 代理建立连接.");
+ }
+ }
+ try
+ {
+ if (messageObj != null)
+ {
+ string json = messageObj.ToString();
+ //string json = JsonConvert.SerializeObject(messageObj);
+ JYResult result = await mqttClient.PublishMessageAsync(new MqttApplicationMessage
+ {
+ Topic = publishTopic,
+ QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
+ Payload = Encoding.UTF8.GetBytes(json),
+ Retain = false,
+ });
+ if (result.IsSuccess)
+ {
+ if (Service1.SwitchLog)
+ {
+ Logger.WriteMqtt($"线程 [{source}] 发送 [{type}] 消息到主题 [{publishTopic}]: {json}");
+ }
+ }
+ else
+ {
+ Logger.WriteMqtt($"线程 [{source}] 发送 [{type}] 消息到主题 [{publishTopic}] 失败: {result.Message}");
+
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"发送消息错误: {ex.Message}");
+ }
+ }
+
+
+ public async Task SendMessageAsync(string source, string publishTopic, string type, string messageObj)
+ {
+ if (!mqttClient.IsConnected)
+ {
+ mqttIsConnected = false;
+
+ Logger.WriteMqtt("客户端未连接到服务器。正在尝试重新连接...");
+
+
+ await ReconnectWithRetry();
+ if (!mqttClient.IsConnected)
+ {
+ throw new InvalidOperationException("无法与 MQTT 代理建立连接.");
+ }
+ }
+ try
+ {
+ if (messageObj != null)
+ {
+ JYResult result = await mqttClient.PublishMessageAsync(new MqttApplicationMessage
+ {
+ Topic = publishTopic,
+ QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
+ Payload = Encoding.UTF8.GetBytes(messageObj),
+ Retain = false,
+ });
+
+ if (result.IsSuccess)
+ {
+ if (Service1.SwitchLog)
+ {
+ Logger.WriteMqtt($"线程 [{source}] 发送 [{type}] 消息到主题 [{publishTopic}]: {messageObj}");
+ }
+ }
+ else
+ {
+ Logger.WriteMqtt($"线程 [{source}] 发送 [{type}] 消息到主题 [{publishTopic}] 失败: {result.Message}");
+
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"发送消息错误: {ex.Message}");
+ }
+ }
+
+
+
+ public void SubscribeMessage(string topic, MessageFormat format = MessageFormat.Text, bool truncateLongMessage = false)
+ {
+ if (!mqttIsConnected)
+ {
+ ConnectMqttAsync().Wait();
+ }
+
+ try
+ {
+ JYResult operateResult = mqttClient.SubscribeMessage(new string[] { topic });
+ if (!operateResult.IsSuccess)
+ {
+ Logger.WriteMqtt($"订阅消息失败: {operateResult.Message}");
+ return;
+ }
+
+ SubscribeTopic subscribeTopic = mqttClient.GetSubscribeTopic(topic);
+ if (subscribeTopic != null)
+ {
+ subscribeTopic.OnMqttMessageReceived += (sender, args) =>
+ {
+ HandleReceivedMessage(args.Topic, args.Payload, format, truncateLongMessage);
+ };
+ Logger.WriteMqtt($"成功订阅主题: {topic}");
+ }
+ else
+ {
+ Logger.WriteMqtt($"无法获取订阅主题 {topic}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"订阅主题时出错 {topic}: {ex.Message}");
+ }
+ }
+
+ private void HandleReceivedMessage(string topic, byte[] payload, MessageFormat format, bool truncateLongMessage)
+ {
+ ReceiveCount++;
+ string text = string.Empty;
+
+ try
+ {
+ switch (format)
+ {
+ case MessageFormat.Binary:
+ text = BitConverter.ToString(payload).Replace("-", " ");
+ break;
+ case MessageFormat.Text:
+ text = Encoding.UTF8.GetString(payload);
+ break;
+ case MessageFormat.Xml:
+ try
+ {
+ text = XElement.Parse(Encoding.UTF8.GetString(payload)).ToString();
+ }
+ catch
+ {
+ text = Encoding.UTF8.GetString(payload);
+ }
+ break;
+ case MessageFormat.Json:
+ try
+ {
+ text = JObject.Parse(Encoding.UTF8.GetString(payload)).ToString();
+ }
+ catch
+ {
+ text = Encoding.UTF8.GetString(payload);
+ }
+ break;
+ }
+ //if (Service1.SwitchLog)
+ //{
+ // LogNet.WriteDebug($"收到主题 {topic}: {text}");
+ //}
+ //if (truncateLongMessage && text.Length > 200)
+ //{
+ // text = text.Substring(0, 200) + "...";
+ //}
+
+ string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
+ OnMessageReceived?.Invoke(timestamp, topic, text);
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"处理收到的消息时出错: {ex.Message}");
+ }
+ }
+
+ public void UnsubscribeMessage(string topic)
+ {
+ try
+ {
+ JYResult operateResult = mqttClient.UnSubscribeMessage(new string[] { topic });
+ if (operateResult.IsSuccess)
+ {
+ Logger.WriteMqtt($"成功取消订阅主题: {topic}");
+ }
+ else
+ {
+ Logger.WriteMqtt($"取消订阅主题失败 {topic}: {operateResult.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"取消订阅主题时出错 {topic}: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 重连
+ ///
+ ///
+ private async Task ReconnectWithRetry()
+ {
+ int attempts = 0;
+ while (!mqttClient.IsConnected && attempts < MaxReconnectAttempts)
+ {
+ try
+ {
+ await mqttClient.ConnectServerAsync();
+ Logger.WriteMqtt("重新连接成功.");
+
+ return;
+ }
+ catch (Exception ex)
+ {
+ attempts++;
+ Logger.WriteMqtt($"重新连接尝试 {attempts} 失败的: {ex.Message}");
+ await Task.Delay(ReconnectDelay);
+ }
+ }
+ if (!mqttClient.IsConnected)
+ {
+ Logger.WriteMqtt("尝试次数达到上限后仍无法重新连接.");
+ }
+ }
+
+
+ private async Task ReconnectAsync()
+ {
+ Logger.WriteMqtt("正在尝试重新连接...");
+ mqttIsConnected = false;
+ int retryCount = 0;
+ const int maxRetries = 5;
+ const int retryDelayMs = 5000;
+
+ while (!mqttIsConnected && retryCount < maxRetries)
+ {
+ try
+ {
+ await ConnectMqttAsync();
+ if (mqttIsConnected)
+ {
+ Logger.WriteMqtt("重新连接成功");
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteMqtt($"重新连接尝试 {retryCount + 1} 失败的: {ex.Message}");
+ }
+
+ retryCount++;
+ await Task.Delay(retryDelayMs);
+ }
+
+ if (!mqttIsConnected)
+ {
+ Logger.WriteMqtt("多次尝试后仍无法重新连接");
+ }
+ }
+
+
+
+ public async Task Disconnect()
+ {
+ await mqttClient.ConnectCloseAsync();
+ mqttIsConnected = false;
+ Logger.WriteMqtt("与 MQTT服务端断开连接!!!");
+ }
+ }
+}
diff --git a/IOT.Services/MqttDataService.cs b/IOT.Services/MqttDataService.cs
new file mode 100644
index 0000000..e6efb3c
--- /dev/null
+++ b/IOT.Services/MqttDataService.cs
@@ -0,0 +1,730 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace WindowsService1
+{
+ public class MqttDataService
+ {
+
+ public static string BU_id;
+ public static string District_id;
+ public static string Factory_id;
+ public static string Production_line_id;
+ public static string Device_name;
+ public static string Topic;
+ public static string Work_Center_id;
+ public class OperationData
+ {
+ public int Item_Value { get; set; }
+ public string Start_Time { get; set; }
+ public string End_Time { get; set; }
+ }
+
+
+ public class MqttMessage
+ {
+ public string bu_id { get; set; }
+ public string district_id { get; set; }
+ public string factory_id { get; set; }
+ public string production_line_id { get; set; }
+ public string production_processes_id { get; set; }
+ public string work_center_id { get; set; }
+ public string station_id { get; set; }
+ public string device_name { get; set; }
+ public List taglist { get; set; }
+ }
+
+ public class TagList
+ {
+ public string device_time { get; set; }
+ public Dictionary collection_items { get; set; }
+ }
+
+
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static MqttMessage CreateMqttMessage(string source, AcquisitionType type, List processedValues,
+ Dictionary> _plcAddresses,
+ Dictionary _collectionGroups)
+ {
+ try
+ {
+ Logger.WriteInfo($"开始创建 MQTT 消息, 类型: {type}, 数据点数量: {processedValues?.Count ?? 0}");
+
+ if (processedValues == null || processedValues.Count == 0)
+ {
+ Logger.WriteInfo("没有需要处理的数据,返回 null");
+ return null;
+ }
+
+ var addresses = _plcAddresses[type];
+ var config = _collectionGroups[type];
+ var collectionItems = new Dictionary();
+
+ // 获取当前时间作为设备时间
+ var currentTime = DateTime.Now;
+ var deviceTimeStr = currentTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
+
+ Logger.WriteInfo($"处理数据类型: {type}, 当前时间: {deviceTimeStr}");
+
+ switch (type)
+ {
+ case AcquisitionType.PPM:
+ ProcessPPMData(processedValues, addresses, config, collectionItems);
+ break;
+
+ case AcquisitionType.Axis:
+ case AcquisitionType.Cylinder:
+ ProcessFloatData(processedValues, addresses, config, currentTime, collectionItems);
+ break;
+
+ default:
+ Logger.WriteError($"未知的数据类型: {type}");
+ return null;
+ }
+
+ if (collectionItems.Count == 0)
+ {
+ Logger.WriteInfo("没有有效的采集项,返回 null");
+ return null;
+ }
+
+ var message = new MqttMessage
+ {
+ bu_id = BU_id,
+ district_id = District_id,
+ factory_id = Factory_id,
+ production_line_id = Production_line_id,
+ production_processes_id = string.Empty,
+ work_center_id = Work_Center_id,
+ station_id = "1",
+ device_name = Device_name,
+ taglist = new List
+ {
+ new TagList
+ {
+ device_time = deviceTimeStr,
+ collection_items = collectionItems
+ }
+ }
+ };
+
+ // 记录创建的消息内容
+ Logger.WriteInfo($"MQTT 消息创建完成: {JsonConvert.SerializeObject(message, Formatting.None)}");
+
+ return message;
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"创建 MQTT 消息时发生错误: {ex.Message}");
+ return null;
+ }
+ }
+
+ ///
+ /// PPM点位数据
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ // 添加静态字段来存储上次发送的数据
+ private static Dictionary _lastSentData = new Dictionary();
+
+ public static void ProcessPPMData(List processedValues, Dictionary addresses, CollectionGroupConfig config, Dictionary collectionItems)
+ {
+ try
+ {
+ Logger.WriteInfo($"开始处理 PPM 数据,数据点数量: {processedValues.Count}");
+
+ // 创建临时字典存储当前所有数据点
+ var tempItems = new Dictionary();
+
+ foreach (var value in processedValues)
+ {
+ var addressPair = addresses.FirstOrDefault(x => x.Key == value.Name);
+ if (!string.IsNullOrEmpty(addressPair.Key))
+ {
+ int itemValue = Convert.ToInt32(ConvertToIntegerWithThreeDecimals(value.MainValue));
+
+ // 计算开始时间 (结束时间 - 时长)
+ var startTime = value.Timestamp.AddMilliseconds(-itemValue);
+
+ var operationData = new OperationData
+ {
+ Item_Value = itemValue,
+ Start_Time = startTime.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ End_Time = value.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
+ };
+
+ // 检查数据是否发生变化
+ bool shouldAdd = true;
+
+ // 检查是否与上次发送的数据重复
+ if (_lastSentData.TryGetValue(addressPair.Key, out var lastData))
+ {
+ var currentEndTime = operationData.End_Time.ToString();
+ var lastEndTime = lastData.End_Time.ToString();
+
+ if (string.Equals(lastEndTime, currentEndTime, StringComparison.OrdinalIgnoreCase))
+ {
+ shouldAdd = false;
+ Logger.WriteInfo($"跳过重复数据点: {addressPair.Key}");
+ continue;
+ }
+ }
+
+ if (shouldAdd)
+ {
+ tempItems[addressPair.Key] = operationData;
+ Logger.WriteInfo($"处理数据点: {addressPair.Key}, 值: {itemValue}, " +
+ $"开始时间: {startTime:yyyy-MM-dd HH:mm:ss.fff}, " +
+ $"结束时间: {operationData.End_Time}");
+ }
+ //if (shouldAdd)
+ //{
+ // // 检查是否与当前批次的其他数据有重复的结束时间
+ // var duplicateEndTimeItem = tempItems.FirstOrDefault(x =>
+ // string.Equals(x.Value.End_Time.ToString(), operationData.End_Time.ToString(), StringComparison.OrdinalIgnoreCase));
+
+ // if (duplicateEndTimeItem.Key != null)
+ // {
+ // Logger.WriteError($"检测到结束时间重复: {operationData.End_Time} 对应地址: {addressPair.Key}, " +
+ // $"与地址 {duplicateEndTimeItem.Key} 的数据重复");
+
+ // // 根据地址的字母顺序决定哪个需要调整时间,确保调整的一致性
+ // if (string.Compare(addressPair.Key, duplicateEndTimeItem.Key) > 0)
+ // {
+ // operationData.End_Time = value.Timestamp.AddMilliseconds(1).ToString("yyyy-MM-dd HH:mm:ss.fff");
+ // Logger.WriteInfo($"调整后的结束时间: {operationData.End_Time}");
+ // }
+ // }
+
+ // tempItems[addressPair.Key] = operationData;
+ // Logger.WriteInfo($"处理数据点: {addressPair.Key}, 值: {itemValue}, " +
+ // $"开始时间: {startTime:yyyy-MM-dd HH:mm:ss.fff}, " +
+ // $"结束时间: {operationData.End_Time}");
+ //}
+ }
+ else
+ {
+ Logger.WriteError($"未找到数据点 {value.Name} 对应的地址配置");
+ }
+ }
+
+ // 将处理后的数据添加到最终结果并更新上次发送的数据
+ foreach (var item in tempItems)
+ {
+ collectionItems[item.Key] = item.Value;
+ _lastSentData[item.Key] = item.Value; // 更新上次发送的数据
+ }
+
+ Logger.WriteInfo($"PPM 数据处理完成,处理后的数据点数量: {collectionItems.Count}");
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"处理 PPM 数据时出错: {ex.Message}");
+ throw;
+ }
+ }
+
+ private static bool IsDataEqual(OperationData data1, OperationData data2, string addressKey)
+ {
+ if (data1 == null || data2 == null)
+ return false;
+
+ bool isEqual = data1.Item_Value == data2.Item_Value &&
+ data1.Start_Time == data2.Start_Time &&
+ data1.End_Time == data2.End_Time;
+
+ if (isEqual)
+ {
+ Logger.WriteInfo($"检测到重复数据 - 地址: {addressKey}, " +
+ $"值: {data1.Item_Value}, " +
+ $"开始时间: {data1.Start_Time}, " +
+ $"结束时间: {data1.End_Time}");
+ }
+
+ return isEqual;
+ }
+
+
+
+ ///
+ /// 预测性点位
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void ProcessFloatData(List processedValues, Dictionary addresses, CollectionGroupConfig config, DateTime endTime, Dictionary collectionItems)
+ {
+ for (int i = 0; i < processedValues.Count && i < config.Length; i++)
+ {
+ string address = $"D{int.Parse(config.StartAddress.Substring(1)) + i * 2}";
+ if (addresses.ContainsValue(address))
+ {
+ var key = addresses.FirstOrDefault(x => x.Value == address).Key;
+ int itemValue = Convert.ToInt32(ConvertToIntegerWithThreeDecimals(processedValues[i].MainValue));
+
+ // 计算开始时间 (结束时间 - 时长)
+ //var startTime = endTime.AddMilliseconds(-itemValue);
+
+ collectionItems[key] = new OperationData
+ {
+ Item_Value = (int)itemValue,
+ Start_Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ End_Time = endTime.ToString("yyyy-MM-dd HH:mm:ss.fff")
+ };
+ }
+ }
+ }
+
+ ///
+ /// 机械臂数据
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static LRobot LRobotMessage(string source, AcquisitionType type, List processedValues, Dictionary> _plcAddresses,
+ Dictionary _collectionGroups)
+ {
+ if (processedValues == null || processedValues.Count == 0)
+ {
+ return null;
+ }
+ var addresses = _plcAddresses[type];
+ var config = _collectionGroups[type];
+ return new LRobot()
+ {
+ bu_id = BU_id,
+ district_id = District_id,
+ factory_id = Factory_id,
+ production_line_id = Production_line_id,
+ production_processes_id = "",
+ work_center_id = "",
+ station_id = "",
+ device_name = "601PWM01",
+ robot_name = "LUR01",
+ taglist = new List
+ {
+ new Taglist//上料动子1
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[0].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[1].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子2
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[2].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[3].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子3
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[4].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[5].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子4
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[6].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[7].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子5
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[8].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[9].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子6
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[10].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[11].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子7
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[13].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[14].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子8
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[16].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[17].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//上料动子9
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[18].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[19].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机1
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[20].MainValue.ToString(),
+ y_value=processedValues[21].MainValue.ToString(),
+ z_value=processedValues[22].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机2
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[23].MainValue.ToString(),
+ y_value=processedValues[24].MainValue.ToString(),
+ z_value=processedValues[25].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机3
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[26].MainValue.ToString(),
+ y_value=processedValues[27].MainValue.ToString(),
+ z_value=processedValues[28].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机4
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[29].MainValue.ToString(),
+ y_value=processedValues[30].MainValue.ToString(),
+ z_value=processedValues[31].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机5
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[32].MainValue.ToString(),
+ y_value=processedValues[33].MainValue.ToString(),
+ z_value=processedValues[34].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机6
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[35].MainValue.ToString(),
+ y_value=processedValues[36].MainValue.ToString(),
+ z_value=processedValues[37].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机7
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[38].MainValue.ToString(),
+ y_value=processedValues[39].MainValue.ToString(),
+ z_value=processedValues[40].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机8
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[41].MainValue.ToString(),
+ y_value=processedValues[42].MainValue.ToString(),
+ z_value=processedValues[43].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机9
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[44].MainValue.ToString(),
+ y_value=processedValues[45].MainValue.ToString(),
+ z_value=processedValues[46].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机10
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[47].MainValue.ToString(),
+ y_value=processedValues[48].MainValue.ToString(),
+ z_value=processedValues[49].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机11
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[50].MainValue.ToString(),
+ y_value=processedValues[51].MainValue.ToString(),
+ z_value=processedValues[52].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//焊机12
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[53].MainValue.ToString(),
+ y_value=processedValues[54].MainValue.ToString(),
+ z_value=processedValues[55].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//下料动子1
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[56].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[57].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//下料动子2
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[58].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[59].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//下料动子3
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[61].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[62].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//下料动子4
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[63].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[64].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//下料动子5
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[65].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[66].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ },
+ new Taglist//下料动子6
+ {
+ device_time=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+ collections_items=new Collection()
+ {
+ robot_status = "1",
+ x_value=processedValues[67].MainValue.ToString(),
+ y_value="0",
+ z_value=processedValues[68].MainValue.ToString(),
+ time_span="2.5",
+ hand_status="1",
+ }
+ }
+
+ }
+
+ };
+ }
+
+
+
+
+ // 辅助方法
+ private static List ConvertToShortList(object[] data)
+ {
+ return data.Select(x => Convert.ToInt16(x)).ToList();
+ }
+
+ private static List ConvertToFloatList(object[] data)
+ {
+ return data.Select(x => Convert.ToSingle(x)).ToList();
+ }
+
+
+ private static object ConvertToIntegerWithThreeDecimals(object value)
+ {
+ try
+ {
+ if (value == null) return 0;
+
+ if (value is float floatValue)
+ {
+ return (int)Math.Round(floatValue * 1000);
+ }
+ else if (value is double doubleValue)
+ {
+ return (int)Math.Round(doubleValue * 1000);
+ }
+ else if (value is decimal decimalValue)
+ {
+ return (int)Math.Round(decimalValue * 1000);
+ }
+ else
+ {
+ // 尝试转换其他类型
+ float converted = Convert.ToSingle(value);
+ return (int)Math.Round(converted * 1000);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"转换值到整数时出错: {ex.Message}, 值: {value}");
+ return 0;
+ }
+ }
+ }
+}
diff --git a/IOT.Services/NLog.config b/IOT.Services/NLog.config
new file mode 100644
index 0000000..4ca60d8
--- /dev/null
+++ b/IOT.Services/NLog.config
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/IOT.Services/PLCDataReader.cs b/IOT.Services/PLCDataReader.cs
new file mode 100644
index 0000000..4bd584b
--- /dev/null
+++ b/IOT.Services/PLCDataReader.cs
@@ -0,0 +1,155 @@
+using JinYuan.DataConvertLib;
+using PLCCommunication.PLCType.Omron;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace WindowsService1
+{
+
+
+ ///
+ /// 定义每个数据读取请求的参数。
+ ///
+ public class PLCDataReadRequest
+ {
+ ///
+ /// PLC起始地址
+ ///
+ public string StartAddress { get; set; }
+ ///
+ /// 读取长度
+ ///
+ public ushort Length { get; set; }
+ ///
+ /// 读取区域名称
+ ///
+ public string StorageName { get; set; }
+ ///
+ /// 读取数据类型
+ ///
+ public PLCDataType DataType { get; set; }
+ }
+
+ ///
+ /// 存储每次读取操作的结果。
+ ///
+ public class PLCDataResult
+ {
+ ///
+ /// 读取区域名称
+ ///
+ public string StorageName { get; set; }
+ ///
+ /// 读取数据类型
+ ///
+ public PLCDataType DataType { get; set; }
+ ///
+ /// 数据
+ ///
+ public byte[] Data { get; set; }
+ ///
+ /// 是否读取成功
+ ///
+ public bool IsSuccess { get; set; }
+ ///
+ /// 读取失败的错误信息
+ ///
+ public string ErrorMessage { get; set; }
+ }
+
+ public class PLCDataReader
+ {
+ private readonly OmronFinsNet _plc;
+
+
+ public PLCDataReader(OmronFinsNet plc)
+ {
+ _plc = plc ?? throw new ArgumentNullException(nameof(plc));
+ }
+
+ public async Task ReadDataAsync(PLCDataReadRequest request)
+ {
+ try
+ {
+ var data = await _plc.ReadAsync(request.StartAddress, request.Length);
+ if (data.IsSuccess && data.Content.Length > 0)
+ {
+ Logger.WriteInfo($"成功读取PLC数据,起始地址:{request.StartAddress},长度:{request.Length},类型:{request.DataType}");
+ return new PLCDataResult
+ {
+ StorageName = request.StorageName,
+ DataType = request.DataType,
+ Data = data.Content,
+ IsSuccess = true
+ };
+ }
+ else
+ {
+ Logger.WriteInfo($"读取PLC数据失败,起始地址:{request.StartAddress},长度:{request.Length},类型:{request.DataType}");
+ return new PLCDataResult
+ {
+ StorageName = request.StorageName,
+ DataType = request.DataType,
+ IsSuccess = false,
+ ErrorMessage = "读取PLC数据失败"
+ };
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"读取PLC数据时发生错误:{ex.Message}");
+ return new PLCDataResult
+ {
+ StorageName = request.StorageName,
+ DataType = request.DataType,
+ IsSuccess = false,
+ ErrorMessage = ex.Message
+ };
+ }
+ }
+
+ private object[] ParseData(byte[] content, PLCDataType dataType)
+ {
+ switch (dataType)
+ {
+ case PLCDataType.Short:
+ return ParseShortData(content);
+ case PLCDataType.Float:
+ return ParseFloatData(content);
+ default:
+ throw new ArgumentException($"不支持的数据类型:{dataType}");
+ }
+ }
+
+ private object[] ParseShortData(byte[] content)
+ {
+ int count = content.Length / 2;
+ return Enumerable.Range(0, count)
+ .Select(i => (object)ShortLib.GetShortFromByteArray(content, i * 2))
+ .ToArray();
+ }
+
+ private object[] ParseFloatData(byte[] content)
+ {
+ int count = content.Length / 4;
+ return Enumerable.Range(0, count)
+ .Select(i => (object)FloatLib.GetFloatFromByteArray(content, i * 4))
+ .ToArray();
+ }
+ }
+
+ public static class ConvertHelper
+ {
+ ///
+ /// 字节数组高低位转换
+ ///
+ ///
+ ///
+ public static byte[] ByteReverse(this byte[] Arrbyte)
+ {
+ byte[] ArrByte = Arrbyte.Select((x, i) => new { x, i }).GroupBy(x => x.i / 2).SelectMany(x => new byte[] { x.Last().x, x.First().x }).ToArray();
+ return ArrByte;
+ }
+ }
+}
diff --git a/IOT.Services/PLCWrapper.cs b/IOT.Services/PLCWrapper.cs
new file mode 100644
index 0000000..f0d3889
--- /dev/null
+++ b/IOT.Services/PLCWrapper.cs
@@ -0,0 +1,104 @@
+using PLCCommunication.PLCType.Omron;
+using System;
+using System.Threading.Tasks;
+
+namespace WindowsService1
+{
+ public class PLCWrapper
+ {
+ private OmronFinsNet _plc;
+ public bool _plcIsConnected;
+ private readonly string _ipAddress;
+ private readonly int _port;
+ private readonly int _maxReconnectAttempts;
+ private readonly int _reconnectDelay;
+ public PLCDataReader _dataReader;
+ public PLCWrapper(string ipAddress, int port, int maxReconnectAttempts = 3, int reconnectDelay = 5000)
+ {
+ _ipAddress = ipAddress;
+ _port = port;
+ _maxReconnectAttempts = maxReconnectAttempts;
+ _reconnectDelay = reconnectDelay;
+ }
+
+ public async Task ConnectAsync()
+ {
+ try
+ {
+ _plc = new OmronFinsNet
+ {
+ IpAddress = _ipAddress,
+ Port = _port
+ };
+ var connectResult = await Task.Run(() => _plc.ConnectServer());
+ _plcIsConnected = connectResult.IsSuccess;
+ if (_plcIsConnected)
+ {
+ _dataReader = new PLCDataReader(GetPLCClient());
+ Logger.WriteInfo("PLC连接成功");
+ }
+ else
+ {
+ Logger.WriteInfo("PLC连接失败");
+ }
+ return _plcIsConnected;
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"PLC连接失败: {ex.Message}");
+ return false;
+ }
+ }
+
+ public void Disconnect()
+ {
+ if (_plcIsConnected)
+ {
+ _plc.ConnectClose();
+ _plcIsConnected = false;
+ Logger.WriteInfo("与PLC断开连接");
+ }
+ }
+
+ public OmronFinsNet GetPLCClient()
+ {
+ if (!_plcIsConnected || _plc == null)
+ {
+ throw new InvalidOperationException("PLC 未连接或未初始化。请先调用 ConnectAsync()。");
+ }
+ return _plc;
+ }
+
+ public async Task ReconnectAsync()
+ {
+ if (_plcIsConnected)
+ {
+ Logger.WriteInfo("PLC已连接,无需重连");
+ return true;
+ }
+
+ for (int attempt = 1; attempt <= _maxReconnectAttempts; attempt++)
+ {
+ Logger.WriteInfo($"尝试重新连接PLC,第 {attempt} 次尝试");
+ if (await ConnectAsync())
+ {
+ Logger.WriteInfo("PLC重新连接成功");
+ return true;
+ }
+
+ if (attempt < _maxReconnectAttempts)
+ {
+ await Task.Delay(_reconnectDelay);
+ }
+ }
+
+ Logger.WriteError($"PLC重新连接失败,已尝试 {_maxReconnectAttempts} 次");
+ return false;
+ }
+
+ public bool IsConnected()
+ {
+ return _plcIsConnected;
+ }
+ }
+}
\ No newline at end of file
diff --git a/IOT.Services/Program.cs b/IOT.Services/Program.cs
new file mode 100644
index 0000000..aeeb614
--- /dev/null
+++ b/IOT.Services/Program.cs
@@ -0,0 +1,20 @@
+using System.ServiceProcess;
+
+namespace WindowsService1
+{
+ internal static class Program
+ {
+ ///
+ /// 应用程序的主入口点。
+ ///
+ static void Main()
+ {
+ ServiceBase[] ServicesToRun;
+ ServicesToRun = new ServiceBase[]
+ {
+ new Service1()
+ };
+ ServiceBase.Run(ServicesToRun);
+ }
+ }
+}
diff --git a/IOT.Services/Properties/AssemblyInfo.cs b/IOT.Services/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..004aa2d
--- /dev/null
+++ b/IOT.Services/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("WindowsService1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("WindowsService1")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("242400f4-9b01-4aee-a6f7-05083757fcdd")]
+
+// 程序集的版本信息由下列四个值组成:
+//
+// 主版本
+// 次版本
+// 生成号
+// 修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/IOT.Services/Service1.Designer.cs b/IOT.Services/Service1.Designer.cs
new file mode 100644
index 0000000..b7b9f85
--- /dev/null
+++ b/IOT.Services/Service1.Designer.cs
@@ -0,0 +1,38 @@
+namespace WindowsService1
+{
+ partial class Service1
+ {
+ ///
+ /// 必需的设计器变量。
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// 清理所有正在使用的资源。
+ ///
+ /// 如果应释放托管资源,为 true;否则为 false。
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ Logger.Shutdown();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region 组件设计器生成的代码
+
+ ///
+ /// 设计器支持所需的方法 - 不要修改
+ /// 使用代码编辑器修改此方法的内容。
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ this.ServiceName = "Service1";
+ }
+
+ #endregion
+ }
+}
diff --git a/IOT.Services/Service1.cs b/IOT.Services/Service1.cs
new file mode 100644
index 0000000..e347afc
--- /dev/null
+++ b/IOT.Services/Service1.cs
@@ -0,0 +1,1179 @@
+using JinYuan.DataConvertLib;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PLCCommunication.MQTT;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.ServiceProcess;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace WindowsService1
+{
+ public partial class Service1 : ServiceBase
+ {
+ private readonly List _tasks = new List();
+ private readonly CancellationTokenSource _cts = new CancellationTokenSource();
+
+ private PLCWrapper _plcWrapper;
+ private MqttClientWrapper mqttClient = null;
+ private System.Threading.Timer _periodicTimer;
+
+ private Dictionary _collectionGroups;
+ private Dictionary> _plcAddresses;
+
+ private AcquisitionType[] AcqTypes = null;
+
+ private const int ReconnectionInterval = 5000; // 5 seconds
+ private const int MaxReconnectAttempts = 3;
+
+ public string PlcIpAddress = "127.0.0.1";
+ public int PlcPort = 9600;
+
+
+ public string MQTTServerAddress = "127.0.0.1";
+ public int MQTTServerPort = 1883;
+ public string ClientId = "601UPW01";
+ //PPM主题
+ public string Topic = "EVE/60J/EQP/NEW_PPM/601UPW01";
+ //PPM其他数据
+ public string Topic1 = "eve/60J/EQP/601UPW01";
+ //直角坐标机械臂
+ public string LRobotTopic = "EVE/60J/LRobot/601PWM01_LUR01";
+ //关节机械臂
+ public string JRobotTopic = "EVE/60J/JRobot/601PWM01_STR01";
+
+ public MqttCredential credentials = null;
+
+ public string BU_id = "EVE2BU";
+ public string District_id = "QJ";
+ public string Factory_id = "60J";
+ public string Production_line_id = "MW-601";
+ public string Device_name = "601UPW01";
+
+ public int ThreadInterval = 50;
+ public static bool SwitchLog = false;
+
+ public Service1()
+ {
+ InitializeComponent();
+ ServiceName = "TestMqttService";
+ CanStop = true; // 服务可以被停止
+ CanPauseAndContinue = false;
+ AutoLog = true;
+
+
+ }
+
+ protected override void OnStart(string[] args)
+ {
+ Logger.WriteInfo($"OnStart:服务启动-{DateTime.Now}");
+ InitializeConfig();
+ _plcWrapper = new PLCWrapper(PlcIpAddress, PlcPort);
+ InitializeTask();
+ InitializePLC();
+ InitializeMQTT();
+
+ Task.Run(() => RunChangeDetectionTaskAsync(_cts.Token));// 启动变化检测任务
+
+
+ Logger.WriteInfo($"OnStart:服务启动完毕-{DateTime.Now}");
+ }
+
+ protected override async void OnStop()
+ {
+ _cts.Cancel();
+ _periodicTimer?.Dispose();
+ // 停止 PLC 读取
+ if (_plcWrapper != null)
+ {
+ _plcWrapper.Disconnect();
+ Logger.WriteInfo("PLC 通讯停止.");
+ }
+ if (mqttClient != null)
+ {
+ await mqttClient.Disconnect();
+ Logger.WriteInfo("MQTT 连接已关闭.");
+ }
+ Logger.WriteInfo("OnStop");
+ }
+
+ ///
+ /// 系统配置文件读取
+ ///
+ private void InitializeConfig()
+ {
+ ReadConfigFromXml();
+ PlcIpAddress = ConfigurationManager.AppSettings["PlcIpAddress"] ?? "127.0.0.1";
+ PlcPort = int.Parse(ConfigurationManager.AppSettings["PlcPort"] ?? "9600");
+
+ MQTTServerAddress = ConfigurationManager.AppSettings["MQTTServerAddress"] ?? "127.0.0.1";
+ MQTTServerPort = int.Parse(ConfigurationManager.AppSettings["MQTTServerPort"] ?? "1883");
+ ClientId = ConfigurationManager.AppSettings["ClientId"] ?? "601UPW01";
+
+ Topic = ConfigurationManager.AppSettings["Topic"] ?? "EVE/60J/EQP/NEW_PPM/601UPW01";
+ Topic1 = ConfigurationManager.AppSettings["Topic1"] ?? "eve/60J/EQP/601UPW01";
+ LRobotTopic = ConfigurationManager.AppSettings["LRobotTopic"] ?? "EVE/60J/LRobot/601PWM01_LUR01";
+ JRobotTopic = ConfigurationManager.AppSettings["JRobotTopic"] ?? "EVE/60J/JRobot/601PWM01_STR01";
+
+ BU_id = ConfigurationManager.AppSettings["BU_id"] ?? "601UPW01";
+ District_id = ConfigurationManager.AppSettings["District_id"] ?? "QJ";
+ Factory_id = ConfigurationManager.AppSettings["Factory_id"] ?? "60J";
+ Production_line_id = ConfigurationManager.AppSettings["Production_line_id"] ?? "MW-601";
+ Device_name = ConfigurationManager.AppSettings["Device_name"] ?? "601UPW01";
+ ThreadInterval = int.Parse(ConfigurationManager.AppSettings["ThreadInterval"] ?? "50");
+ SwitchLog = bool.Parse(ConfigurationManager.AppSettings["SwitchLog"] ?? "True");
+ MqttDataService.BU_id = BU_id;
+ MqttDataService.District_id = District_id;
+ MqttDataService.Factory_id = Factory_id;
+ MqttDataService.Production_line_id = Production_line_id;
+ MqttDataService.Device_name = Device_name;
+ MqttDataService.Work_Center_id = "601FGE02";
+
+ Logger.WriteInfo("读取系统配置文件成功!");
+
+
+ }
+
+ private void ReadConfigFromXml()
+ {
+ try
+ {
+ var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.xml");
+ var doc = XDocument.Load(configPath);
+ string deviceName = "";
+ _plcAddresses = new Dictionary>();
+ _collectionGroups = new Dictionary();
+
+ foreach (var deviceElement in doc.Descendants("Device"))
+ {
+ var isEnabled = bool.Parse(deviceElement.Attribute("isEnable")?.Value ?? "false");
+
+ if (isEnabled)
+ {
+ deviceName = deviceElement.Attribute("name")?.Value;
+
+ foreach (var groupElement in deviceElement.Elements("CollectionGroup"))
+ {
+ var groupName = groupElement.Attribute("name").Value;
+ Logger.WriteInfo($"添加寄存器组类型: {groupName}");
+
+ var acquisitionType = (AcquisitionType)Enum.Parse(typeof(AcquisitionType), groupName);
+ var groupConfig = new CollectionGroupConfig
+ {
+ Name = groupName,
+ StartAddress = groupElement.Attribute("StartAddress").Value,
+ Length = int.Parse(groupElement.Attribute("length").Value),
+ DefaultDataType = groupElement.Attribute("DataType")?.Value ?? "Short",
+ Types = new Dictionary()
+ };
+
+ var groupAddresses = new Dictionary();
+
+ foreach (var typeElement in groupElement.Elements("Type"))
+ {
+ var typeName = typeElement.Attribute("name").Value;
+ var typeAddress = typeElement.Attribute("address")?.Value ?? typeElement.Value;
+ var dataType = typeElement.Attribute("DataType")?.Value ?? groupConfig.DefaultDataType;
+
+ // 创建TypeConfig对象
+ var typeConfig = new TypeConfig
+ {
+ Name = typeName,
+ Address = typeAddress,
+ DataType = dataType
+ };
+
+ groupConfig.Types[typeName] = typeConfig;
+ groupAddresses[typeName] = typeAddress;
+
+ Logger.WriteInfo($"添加类型: {typeName}, 地址: {typeAddress}, 数据类型: {dataType}");
+ }
+
+ _collectionGroups[acquisitionType] = groupConfig;
+ _plcAddresses[acquisitionType] = groupAddresses;
+ }
+ break; // 只处理第一个启用的Device
+ }
+ }
+
+ Logger.WriteInfo($"读取 【{deviceName}】地址文件成功!!!");
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"读取配置文件错误: {ex.Message}");
+ }
+ }
+
+
+
+
+ private static PLCDataType DetermineDataType(string groupName, string dataTypeAttribute)
+ {
+ if (!string.IsNullOrEmpty(dataTypeAttribute))
+ {
+ return (PLCDataType)Enum.Parse(typeof(PLCDataType), dataTypeAttribute);
+ }
+ if (!string.IsNullOrEmpty(groupName))
+ {
+ AcquisitionType type = (AcquisitionType)Enum.Parse(typeof(AcquisitionType), groupName);
+ // 如果 DataType 属性为空,根据组名推断类型
+ switch (type)
+ {
+ case AcquisitionType.PPM:
+ return PLCDataType.Short;
+ case AcquisitionType.Axis:
+ case AcquisitionType.Cylinder:
+ return PLCDataType.Float;
+ default:
+ throw new ArgumentException($"未知的组名: {groupName}");
+ }
+
+ }
+ else
+ {
+ throw new ArgumentException($"未知的组名: {groupName}");
+ }
+ }
+
+
+ #region 初始化Task线程,PLC,MQTT
+ private void InitializeTask()
+ {
+ if (_collectionGroups != null && _collectionGroups.Count > 0)
+ {
+ List typesList = new List();
+ foreach (var kvp in _collectionGroups)
+ {
+ switch (kvp.Key)
+ {
+ case AcquisitionType.Axis:
+ case AcquisitionType.Cylinder:
+ StartPeriodicTask(); // 启动定时读取任务
+ break;
+ case AcquisitionType.PPM:
+ typesList.Add(kvp.Key);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // 将 List 转换为数组
+ AcqTypes = typesList.ToArray();
+
+ // 如果需要,你可以在这里使用 types 数组
+ if (AcqTypes.Length > 0)
+ {
+ Logger.WriteInfo("添加类型: " + string.Join(", ", AcqTypes));
+ // 这里可以添加其他使用 types 数组的逻辑
+ }
+ }
+ }
+
+
+
+
+ private async void InitializePLC()
+ {
+ try
+ {
+ bool connected = await _plcWrapper.ConnectAsync();
+ if (connected)
+ {
+ Logger.WriteInfo("PLC初始化并连接成功.");
+ }
+ else
+ {
+ Logger.WriteInfo("无法连接 PLC.");
+ throw new Exception("PLC 连接失败");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"初始化 PLC 错误: {ex.Message}");
+ }
+ }
+
+ private async void InitializeMQTT()
+ {
+ try
+ {
+ mqttClient = new MqttClientWrapper(MQTTServerAddress, MQTTServerPort, ClientId, credentials); //初始化
+ await mqttClient.InitializeAndConnectAsync();//连接MQTT
+ SubscribeTopic(Topic);//订阅主题
+ SubscribeTopic(Topic1);//订阅主题1
+ SubscribeTopic(LRobotTopic);//订阅直角坐标机械臂
+ SubscribeTopic(JRobotTopic);//订阅关机机械臂
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"连接 MQTT 代理时出错: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 订阅主题
+ ///
+ ///
+ private void SubscribeTopic(string topicToSubscribe)
+ {
+ mqttClient.OnMessageReceived += (timestamp, topic, message) =>
+ {
+ //Console.WriteLine($"{timestamp} Topic[{topic}]: {message}");
+ //Logger.WriteMqtt($"{timestamp} Topic[{topic}]");
+ // 在这里更新你的 UI
+ };
+ mqttClient.SubscribeMessage(topicToSubscribe, MqttClientWrapper.MessageFormat.Json, true);
+ }
+
+
+ ///
+ /// 类发布消息
+ ///
+ ///
+ ///
+ ///
+ private async Task SendMqttAsync(string source, string publishTopic, string type, JObject messageObj)
+ {
+ try
+ {
+ if (publishTopic != null)
+ {
+
+ await mqttClient.SendMessageAsync(source, publishTopic, type, messageObj);
+ }
+ }
+ catch (Exception ex)
+ {
+
+ Console.WriteLine($"发布信息: {ex.Message}");
+ }
+
+ }
+
+
+ ///
+ /// 字符串发布消息
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task SendMqttAsync(string source, string publishTopic, string type, string messageObj)
+ {
+ try
+ {
+ if (publishTopic != null)
+ {
+
+
+ await mqttClient.SendMessageAsync(source, publishTopic, type, messageObj);
+ }
+ }
+ catch (Exception ex)
+ {
+
+ Console.WriteLine($"发布信息: {ex.Message}");
+ }
+
+ }
+ #endregion
+
+
+ #region 创建定时线程
+ ///
+ /// 轴扭矩定时器采集线程
+ ///
+ private void StartPeriodicTask()
+ {
+ Logger.WriteInfo("启动定期数据收集任务");
+ _periodicTimer = new System.Threading.Timer(async _ =>
+ {
+ try
+ {
+ Logger.WriteInfo($"定期触发任务: {DateTime.Now}");
+ await CollectAndPublishData("AxisTask", new[] { AcquisitionType.Axis });
+ await CollectAndPublishData("AxisTask", new[] { AcquisitionType.Cylinder });
+ await CollectAndPublishData("LRobotTask", new[] { AcquisitionType.LRobot });
+ Logger.WriteInfo($"定期任务完成: {DateTime.Now}");
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"周期性任务执行错误: {ex.Message}");
+ }
+ }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); // 每秒执行一次
+ }
+
+ ///
+ /// 有变化数据采集线程
+ ///
+ ///
+ ///
+ ///
+
+ private async Task RunChangeDetectionTaskAsync(CancellationToken ct)
+ {
+ while (!ct.IsCancellationRequested)
+ {
+ if (_plcWrapper._plcIsConnected)
+ {
+ try
+ {
+ await CollectAndPublishData("ChangeTask", AcqTypes);
+ await Task.Delay(500, ct);
+ }
+ catch (OperationCanceledException)
+ {
+ // 正常取消,无需额外处理
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"变更检测任务错误: {ex.Message}");
+ await Task.Delay(1000, ct);
+ }
+ }
+ }
+ }
+ #endregion
+
+ ///
+ /// 读取数据
+ ///
+ ///
+ ///
+ ///
+ private async Task CollectAndPublishData(string source, AcquisitionType[] types)
+ {
+ const int maxRetries = 3;
+ for (int retry = 0; retry < maxRetries; retry++)
+ {
+ try
+ {
+ var requests = new List();
+ foreach (var type in types)
+ {
+ var config = _collectionGroups[type];
+ requests.Add(new PLCDataReadRequest
+ {
+ StartAddress = config.StartAddress,
+ Length = (ushort)config.Length,
+ StorageName = config.Name
+ });
+ }
+
+ foreach (var request in requests)
+ {
+ var result = await _plcWrapper._dataReader.ReadDataAsync(request);
+ if (result.IsSuccess)
+ {
+ await ProcessPLCData(source, request.StorageName, result.Data);
+ }
+ else
+ {
+ Logger.WriteInfo($"错误读取 {result.StorageName}: {result.ErrorMessage}");
+ throw new Exception($"读取 PLC 数据失败: {result.ErrorMessage}");
+ }
+ }
+ break; // 如果成功,退出重试循环
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"Error {source}: {ex.Message}");
+ if (retry < maxRetries - 1)
+ {
+ Logger.WriteInfo($"尝试重新连接到 PLC,尝试 {retry + 1}");
+ bool reconnected = await AttemptReconnection(_cts.Token);
+ if (!reconnected)
+ {
+ Logger.WriteError("无法重新连接 PLC,放弃进一步尝试");
+ break;
+ }
+ }
+ else
+ {
+ Logger.WriteError($"最大重试次数 ({maxRetries}) 已到达,放弃操作");
+ }
+ }
+ }
+ }
+
+ ///
+ /// 重连PLC
+ ///
+ ///
+ ///
+ private async Task AttemptReconnection(CancellationToken ct)
+ {
+ const int MaxReconnectAttempts = 3;
+ const int ReconnectionInterval = 5000; // 5 seconds
+
+ for (int attempt = 1; attempt <= MaxReconnectAttempts; attempt++)
+ {
+ if (ct.IsCancellationRequested)
+ {
+ return false;
+ }
+
+ Logger.WriteInfo($"尝试重新连接PLC,第 {attempt} 次尝试");
+ bool reconnected = await _plcWrapper.ReconnectAsync();
+ if (reconnected)
+ {
+ Logger.WriteInfo("PLC重新连接成功");
+ return true;
+ }
+
+ if (attempt < MaxReconnectAttempts)
+ {
+ await Task.Delay(ReconnectionInterval, ct);
+ }
+ }
+ Logger.WriteError($"PLC重新连接失败,已尝试 {MaxReconnectAttempts} 次");
+ return false;
+ }
+
+
+ ///
+ /// 读取PLC数据
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task ProcessPLCData(string source, string acquisitionTypeName, byte[] data)
+ {
+ try
+ {
+ AcquisitionType type = (AcquisitionType)Enum.Parse(typeof(AcquisitionType), acquisitionTypeName);
+ var typeConfigs = GetTypeConfigs(type).ToList();
+ bool hasChanges = false;
+ List processedValues = null;
+
+ switch (type)
+ {
+ case AcquisitionType.PPM:
+ var ppmResult = ProcessShortData(type, data, typeConfigs);
+ hasChanges = ppmResult.hasChanges;
+ processedValues = ppmResult.values;
+ break;
+
+ case AcquisitionType.Axis:
+ case AcquisitionType.Cylinder:
+ case AcquisitionType.LRobot:
+ var floatResult = ProcessFloatData(type, data, typeConfigs);
+ hasChanges = floatResult.hasChanges;
+ processedValues = floatResult.values;
+ break;
+
+ default:
+ Logger.WriteError($"未知的采集类型: {type}");
+ return;
+ }
+
+ if (hasChanges && processedValues != null)
+ {
+
+ // 上传到MQTT服务器
+ await UpMQTTServerDataNew(source, type, processedValues);
+ }
+ else
+ {
+ Logger.WriteInfo($"{type} 数据未改变,未上传");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"处理 PLC 数据时出错(源: {source}, 类型: {acquisitionTypeName}): {ex.Message}");
+ }
+ }
+
+ // 相应地,ProcessFloatData方法也需要修改返回类型
+
+ private List _lastFloatStationData = new List();
+ private (bool hasChanges, List values) ProcessFloatData(AcquisitionType type, byte[] rawData, List typeConfigs)
+ {
+ bool hasChanges = false;
+ var currentValues = new DataValue[typeConfigs.Count];
+
+ // 记录实际需要处理的数据点数量
+ Logger.WriteInfo($"开始处理数据: typeConfigs.Count={typeConfigs.Count}, rawData.Length={rawData.Length}");
+ var sw = new Stopwatch();
+ sw.Restart();
+ // 初始化数组为完整的大小
+ currentValues = new DataValue[typeConfigs.Count];
+ var stationDataList = new List();
+
+ try
+ {
+ // 第一步:处理所有Float类型数据(主数据)
+ var floatConfigs = typeConfigs.Where(t => t.DataType.Equals("Float", StringComparison.OrdinalIgnoreCase)).ToList();
+ foreach (var config in floatConfigs)
+ {
+ // 从地址中提取偏移量
+ if (int.TryParse(config.Address.Substring(1), out int address))
+ {
+ int startAddress = int.Parse(typeConfigs[0].Address.Substring(1));
+ int offset = (address - startAddress) * 2;
+
+ if (offset >= 0 && offset + 4 <= rawData.Length)
+ {
+ byte[] valueBytes = new byte[4];
+ for (int j = 0; j < 4; j++)
+ {
+ valueBytes[j] = (byte)(Convert.ToInt32(rawData[offset + j]) & 0xFF);
+ }
+
+ object convertedValue = ConvertFromBytes(valueBytes, "Float");
+ int index = typeConfigs.IndexOf(config);
+ currentValues[index] = new DataValue
+ {
+ Name = config.Name,
+ Value = convertedValue,
+ DataType = config.DataType
+ };
+ }
+ }
+ }
+
+ // 第二步:处理所有Short类型数据
+ var shortConfigs = typeConfigs.Where(t => t.DataType.Equals("Short", StringComparison.OrdinalIgnoreCase)).ToList();
+ foreach (var config in shortConfigs)
+ {
+ if (int.TryParse(config.Address.Substring(1), out int address))
+ {
+ int startAddress = int.Parse(typeConfigs[0].Address.Substring(1));
+ int offset = (address - startAddress) * 2;
+
+ if (offset >= 0 && offset + 2 <= rawData.Length)
+ {
+ byte[] valueBytes = new byte[2];
+ for (int j = 0; j < 2; j++)
+ {
+ valueBytes[j] = (byte)(Convert.ToInt32(rawData[offset + j]) & 0xFF);
+ }
+
+ object convertedValue = ConvertFromBytes(valueBytes, "Short");
+ int index = typeConfigs.IndexOf(config);
+ currentValues[index] = new DataValue
+ {
+ Name = config.Name,
+ Value = convertedValue,
+ DataType = config.DataType
+ };
+ }
+ }
+ }
+
+ // 第三步:组织StationData列表
+
+ foreach (var floatConfig in floatConfigs)
+ {
+ string baseName = floatConfig.Name; // 例如 "S_MagLev"
+ var mainValue = currentValues[typeConfigs.IndexOf(floatConfig)]?.Value;
+ // 先获取MainValue,如果为0则跳过这个工位,暂时不过滤
+ //if (mainValue == null || Convert.ToDouble(mainValue) == 0)
+ //{
+ // Logger.WriteInfo($"跳过工位 {baseName}: MainValue为0或null");
+ // continue;
+ //}
+
+ // 只检查MainValue是否与上一次的值相同,暂时不检查
+ //var lastData = _lastStationData.FirstOrDefault(x => x.Name == baseName);
+ //if (lastData != null)
+ //{
+ // bool valueEqual = Convert.ToDouble(lastData.MainValue) == Convert.ToDouble(mainValue);
+
+ // if (valueEqual)
+ // {
+ // Logger.WriteInfo($"跳过工位 {baseName}: 值未变化 当前值:{mainValue}");
+ // continue;
+ // }
+
+ // Logger.WriteInfo($"工位 {baseName} 数值发生变化: {lastData.MainValue} -> {mainValue}");
+ //}
+
+ // 创建StationData对象
+ var stationData = new StationData
+ {
+ Name = baseName,
+ MainValue = mainValue,
+ Timestamp = DateTime.Now,
+ };
+
+ stationDataList.Add(stationData);
+ Logger.WriteInfo($"添加工位数据: {stationData.Name}, 值: {stationData.MainValue}, 时间戳: {stationData.Timestamp:HH:mm:ss.fff}");
+ }
+
+ // 检查是否有新数据需要更新
+ if (stationDataList.Any())
+ {
+ hasChanges = true; // 如果有数据通过过滤,就表示有变化
+
+ // 创建深拷贝以避免引用问题
+ _lastFloatStationData = stationDataList.Select(s => new StationData
+ {
+ Name = s.Name,
+ MainValue = s.MainValue,
+ Timestamp = s.Timestamp
+ }).ToList();
+
+ _lastFloatStationData = stationDataList;//把当前的新数据赋值到旧的集合中
+
+
+ //// 记录变化信息
+ //foreach (var newData in stationDataList)
+ //{
+ // Logger.WriteInfo($"工位 {newData.Name} 更新: 时间戳: {newData.Timestamp:HH:mm:ss.fff}, 值: {newData.MainValue}");
+ //}
+ }
+ else
+ {
+ Logger.WriteInfo("没有发现时间戳变化的数据");
+ hasChanges = false;
+ }
+
+
+ sw.Stop();
+ Logger.WriteInfo($"处理结束耗时:{sw.ElapsedMilliseconds}ms");
+ return (hasChanges, stationDataList);
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"处理数据时出错: {ex.Message}");
+ return (false, stationDataList);
+ }
+
+ }
+
+
+
+
+ ///
+ /// 检测PPM数据是否有变化
+ ///
+ ///
+ ///
+ private List _lastStationData = new List();
+
+ private readonly StationDataComparer2 _comparer = new StationDataComparer2();
+
+ private (bool hasChanges, List values) ProcessShortData(AcquisitionType type, byte[] rawData, List typeConfigs)
+ {
+ var sw = new Stopwatch();
+ sw.Start();
+ Logger.WriteInfo($"开始新一轮数据处理");
+
+ try
+ {
+ var currentStationData = new Dictionary();
+ bool hasNewData = false;
+
+ // 1. 获取所有主值配置(Float类型)
+ var mainValueConfigs = typeConfigs
+ .Where(t => string.Equals(t.DataType, "Float", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(t => int.Parse(t.Address.Substring(1)))
+ .ToList();
+
+ Logger.WriteInfo($"找到 {mainValueConfigs.Count} 个Float类型配置");
+ foreach (var config in mainValueConfigs)
+ {
+ Logger.WriteInfo($"Float配置: {config.Name}, 地址: {config.Address}");
+ }
+
+ // 2. 获取所有时间配置(Short类型)
+ var timeConfigs = typeConfigs
+ .Where(t => string.Equals(t.DataType, "Short", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ // 3. 首先读取所有工位的当前值和时间戳
+ foreach (var mainConfig in mainValueConfigs)
+ {
+ string stationName = mainConfig.Name;
+
+ // 读取主值
+ double? mainValue = ReadFloatValue(mainConfig, rawData, typeConfigs[0].Address);
+ if (mainValue == null || mainValue <= 0)
+ {
+ Logger.WriteInfo($"工位 {stationName} 主值无效: {mainValue}");
+ continue;
+ }
+
+ // 读取时间组件
+ DateTime timestamp = ReadTimestamp(stationName, timeConfigs, rawData, typeConfigs[0].Address);
+
+ // 添加到当前数据集合
+ currentStationData[stationName] = new StationData
+ {
+ Name = stationName,
+ MainValue = mainValue,
+ Timestamp = timestamp
+ };
+
+ Logger.WriteInfo($"读取工位数据: {stationName}, 值: {mainValue}, 时间: {timestamp:HH:mm:ss.fff}");
+ }
+
+ // 4. 比较新旧数据,确定是否有变化
+ if (currentStationData.Any())
+ {
+ if (_lastStationData == null || !_lastStationData.Any())
+ {
+ hasNewData = true;
+ Logger.WriteInfo("首次数据采集,记录所有数据");
+ }
+ else
+ {
+ // 检查每个工位的数据是否有变化
+ foreach (var current in currentStationData.Values)
+ {
+ var lastData = _lastStationData.FirstOrDefault(x => x.Name == current.Name);
+ if (lastData == null)
+ {
+ hasNewData = true;
+ Logger.WriteInfo($"发现新工位数据: {current.Name}");
+ break;
+ }
+
+ // 检查值和时间戳是否都有变化
+ bool valueChanged = Math.Abs(Convert.ToDouble(lastData.MainValue) - Convert.ToDouble(current.MainValue)) > 0.001;
+ bool timeChanged = Math.Abs((current.Timestamp - lastData.Timestamp).TotalMilliseconds) >= 500;
+
+ if (valueChanged || timeChanged)
+ {
+ hasNewData = true;
+ Logger.WriteInfo($"工位 {current.Name} 数据发生变化 - 值变化: {valueChanged}, 时间变化: {timeChanged}");
+ break;
+ }
+ }
+ }
+ }
+
+ // 5. 如果有新数据,更新最后处理的数据
+ var resultList = currentStationData.Values.ToList();
+ if (hasNewData)
+ {
+ _lastStationData = new List(resultList);
+ Logger.WriteInfo($"更新状态数据,数量: {resultList.Count}");
+ }
+
+ sw.Stop();
+ Logger.WriteInfo($"处理完成,耗时: {sw.ElapsedMilliseconds}ms, 数据点数量: {resultList.Count}, 有变化: {hasNewData}");
+
+ return (hasNewData, resultList);
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"数据处理错误: {ex.Message}\n{ex.StackTrace}");
+ return (false, new List());
+ }
+ }
+
+ private double? ReadFloatValue(TypeConfig config, byte[] rawData, string baseAddress)
+ {
+ try
+ {
+ if (!int.TryParse(config.Address.Substring(1), out int address) ||
+ !int.TryParse(baseAddress.Substring(1), out int startAddress))
+ {
+ return null;
+ }
+
+ int offset = (address - startAddress) * 2;
+ if (offset < 0 || offset + 4 > rawData.Length)
+ {
+ return null;
+ }
+
+ byte[] valueBytes = new byte[4];
+ Array.Copy(rawData, offset, valueBytes, 0, 4);
+ var value = ConvertFromBytes(valueBytes, "Float");
+ return value != null ? Convert.ToDouble(value) : null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private DateTime ReadTimestamp(string stationName, List timeConfigs, byte[] rawData, string baseAddress)
+ {
+ // 获取时间组件配置
+ var hourConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_Hour");
+ var minConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_Min");
+ var secConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_Sec");
+ var msecConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_MSec");
+
+ // 读取时间值
+ int hour = ReadShortValue(hourConfig, rawData, baseAddress);
+ int minute = ReadShortValue(minConfig, rawData, baseAddress);
+ int second = ReadShortValue(secConfig, rawData, baseAddress);
+ int millisecond = ReadShortValue(msecConfig, rawData, baseAddress);
+
+ // 验证时间值
+ hour = Math.Min(Math.Max(hour, 0), 23);
+ minute = Math.Min(Math.Max(minute, 0), 59);
+ second = Math.Min(Math.Max(second, 0), 59);
+ millisecond = Math.Min(Math.Max(millisecond, 0), 999);
+
+ // 获取当前时间
+ var now = DateTime.Now;
+
+ // 构建读取的时间
+ var readTime = new DateTime(
+ now.Year,
+ now.Month,
+ now.Day,
+ hour,
+ minute,
+ second,
+ millisecond
+ );
+
+ // 如果读取的时间比当前时间大,说明应该是前一天的数据
+ if (readTime > now)
+ {
+ readTime = readTime.AddDays(-1);
+ Logger.WriteInfo($"检测到跨天数据,调整日期为前一天: {readTime:yyyy-MM-dd HH:mm:ss.fff}");
+ }
+
+ return readTime;
+ }
+
+ private int ReadShortValue(TypeConfig config, byte[] rawData, string baseAddress)
+ {
+ try
+ {
+ if (config == null || !int.TryParse(config.Address.Substring(1), out int address) ||
+ !int.TryParse(baseAddress.Substring(1), out int startAddress))
+ {
+ return 0;
+ }
+
+ int offset = (address - startAddress) * 2;
+ if (offset < 0 || offset + 2 > rawData.Length)
+ {
+ return 0;
+ }
+
+ byte[] valueBytes = new byte[2];
+ Array.Copy(rawData, offset, valueBytes, 0, 2);
+ var value = ConvertFromBytes(valueBytes, "Short");
+ return Convert.ToInt32(value);
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+
+
+ private object ConvertFromBytes(byte[] bytes, string dataType)
+ {
+ try
+ {
+ if (bytes == null || bytes.Length == 0)
+ return null;
+
+ switch (dataType.ToLower())
+ {
+ case "bool":
+ return BitConverter.ToInt16(bytes, 0) != 0;
+ case "short":
+ return ShortLib.GetShortFromByteArray(bytes, 0, DataFormat.CDAB);
+ case "int":
+ return IntLib.GetIntFromByteArray(bytes, 0, DataFormat.CDAB);
+ case "float":
+ return FloatLib.GetFloatFromByteArray(bytes, 0, DataFormat.CDAB);
+ default:
+ return ShortLib.GetShortFromByteArray(bytes, 0, DataFormat.CDAB);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"转换数据类型失败: {BitConverter.ToString(bytes)} to {dataType}, {ex.Message}");
+ return null;
+ }
+ }
+
+
+
+ ///
+ /// 上传MQTT
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task UpMQTTServerDataNew(string source, AcquisitionType type, List processedValues)
+ {
+ try
+ {
+ if (processedValues == null || processedValues.Count == 0)
+ {
+ return;
+ }
+
+ //var addresses = _plcAddresses[type];
+ //var config = _collectionGroups[type];
+
+ var message = MqttDataService.CreateMqttMessage(source, type, processedValues, _plcAddresses, _collectionGroups);
+
+ if (message != null)
+ {
+ var messageJson = JsonConvert.SerializeObject(message, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore,
+ Formatting = Formatting.None
+ });
+ if (type.ToString() == "PPM")
+ {
+ await SendMqttAsync(source, Topic, type.ToString(), messageJson);
+ }
+ else if (type.ToString() == "Axis" || type.ToString() == "Cylinder")
+ {
+ await SendMqttAsync(source, Topic1, type.ToString(), messageJson);
+ }
+ }
+ if (type.ToString() == "LRobot")
+ {
+ var message1 = MqttDataService.LRobotMessage(source, type, processedValues, _plcAddresses, _collectionGroups);
+ if (message1 != null)
+ {
+ var messageJson1 = JsonConvert.SerializeObject(message1, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore,
+ Formatting = Formatting.None
+ });
+ if (type.ToString() == "LRobot")
+ {
+ await SendMqttAsync(source, LRobotTopic, type.ToString(), messageJson1);
+ }
+ }
+ }
+
+ }
+ catch (Exception ex)
+ {
+ Logger.WriteError($"方法:上传MQTTServer数据,上传失败: {ex.Message}", ex);
+ }
+ }
+
+
+
+
+ private List ConvertToShortList(object[] data)
+ {
+ return data.Select(d => Convert.ToInt16(d)).ToList();
+ }
+
+ private List ConvertToFloatList(object[] data)
+ {
+ return data.Select(d => Convert.ToSingle(d)).ToList();
+ }
+
+ // 获取特定类型的数据类型
+ public string GetDataType(AcquisitionType acquisitionType, string typeName)
+ {
+ if (_collectionGroups.TryGetValue(acquisitionType, out var groupConfig))
+ {
+ if (groupConfig.Types.TryGetValue(typeName, out var typeConfig))
+ {
+ return typeConfig.DataType;
+ }
+ }
+ return "Short"; // 默认返回Short类型
+ }
+
+ // 获取数据类型的字节大小
+ public int GetTypeSize(string dataType)
+ {
+ switch (dataType.ToLower())
+ {
+ case "bool":
+ return 2; // 在PLC中通常用2字节表示
+ case "short":
+ return 2; // 2字节
+ case "int":
+ return 4; // 4字节
+ case "float":
+ return 4; // 4字节
+ default:
+ return 2; // 默认2字节
+ }
+ }
+
+ // 获取组内所有类型配置
+ public IEnumerable GetTypeConfigs(AcquisitionType acquisitionType)
+ {
+ if (_collectionGroups.TryGetValue(acquisitionType, out var groupConfig))
+ {
+ return groupConfig.Types.Values;
+ }
+ return Enumerable.Empty();
+ }
+
+
+ }
+
+
+ ///
+ /// 时间转化
+ ///
+
+ public static class DateTimeExtensions
+ {
+ public static long ToUnixTimeMilliseconds(this DateTime dateTime)
+ {
+ return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
+ }
+
+ public static long GetUnixTimestamp()
+ {
+ return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
+ }
+ }
+
+ // 辅助类
+ public class DataValue
+ {
+ public string Name { get; set; }
+ public object Value { get; set; }
+ public string DataType { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is DataValue other)
+ {
+ if (Value == null && other.Value == null) return true;
+ if (Value == null || other.Value == null) return false;
+
+ switch (DataType.ToLower())
+ {
+ case "float":
+ if (Value is float f1 && other.Value is float f2)
+ return Math.Abs(f1 - f2) < 0.000001f;
+ break;
+ case "bool":
+ if (Value is bool b1 && other.Value is bool b2)
+ return b1 == b2;
+ break;
+ }
+
+ return Value.Equals(other.Value);
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return Value?.GetHashCode() ?? 0;
+ }
+ }
+}
diff --git a/IOT.Services/StationDataComparer.cs b/IOT.Services/StationDataComparer.cs
new file mode 100644
index 0000000..0963233
--- /dev/null
+++ b/IOT.Services/StationDataComparer.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+
+namespace WindowsService1
+{
+ ///
+ /// 判断工位耗时变化
+ ///
+ public class StationDataComparer : IEqualityComparer
+ {
+ private const float FloatEpsilon = 0.000001f; // 可以调整精度
+
+ public bool Equals(StationData x, StationData y)
+ {
+ if (ReferenceEquals(x, y)) return true;
+ if (x is null || y is null) return false;
+
+ // 比较名称
+ if (x.Name != y.Name) return false;
+
+ // 比较主值
+ if (x.MainValue is float xFloat && y.MainValue is float yFloat)
+ {
+ if (Math.Abs(xFloat - yFloat) > FloatEpsilon) return false;
+ }
+ else if (x.MainValue is double xDouble && y.MainValue is double yDouble)
+ {
+ if (Math.Abs(xDouble - yDouble) > FloatEpsilon) return false;
+ }
+ else if (!Equals(x.MainValue, y.MainValue))
+ {
+ return false;
+ }
+
+ // 比较时间戳(可以选择是否忽略毫秒)
+ return Math.Abs((x.Timestamp - y.Timestamp).TotalMilliseconds) < 1;
+ }
+
+ public int GetHashCode(StationData obj)
+ {
+ if (obj is null) return 0;
+
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + (obj.Name?.GetHashCode() ?? 0);
+
+ // 对于浮点数,可以先转换为整数再计算哈希
+ if (obj.MainValue is float f)
+ {
+ hash = hash * 23 + ((int)(f * 1000000)).GetHashCode();
+ }
+ else
+ {
+ hash = hash * 23 + (obj.MainValue?.GetHashCode() ?? 0);
+ }
+
+ hash = hash * 23 + obj.Timestamp.GetHashCode();
+ return hash;
+ }
+ }
+ }
+
+ ///
+ /// 判断时间戳变化
+ ///
+ public class StationDataComparer2 : IEqualityComparer
+ {
+ public bool Equals(StationData x, StationData y)
+ {
+ if (ReferenceEquals(x, y)) return true;
+ if (x is null || y is null) return false;
+
+ // 只比较名称和时间戳
+ if (x.Name != y.Name) return false;
+
+ // 比较时间戳,检查是否完全相同
+ return x.Timestamp == y.Timestamp;
+ }
+
+ public int GetHashCode(StationData obj)
+ {
+ if (obj is null) return 0;
+
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + (obj.Name?.GetHashCode() ?? 0);
+ hash = hash * 23 + obj.Timestamp.GetHashCode();
+ return hash;
+ }
+ }
+ }
+}
diff --git a/IOT.Services/XmlConfigReader.cs b/IOT.Services/XmlConfigReader.cs
new file mode 100644
index 0000000..4a95ee2
--- /dev/null
+++ b/IOT.Services/XmlConfigReader.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Xml.Linq;
+
+namespace WindowsService1
+{
+ public class XmlConfigReader
+ {
+ private Dictionary>> deviceData;
+
+ public XmlConfigReader(string xmlContent)
+ {
+ XDocument doc = XDocument.Parse(xmlContent);
+ deviceData = ReadXmlToNestedDictionary(doc);
+ }
+
+ public XmlConfigReader(XDocument doc)
+ {
+ deviceData = ReadXmlToNestedDictionary(doc);
+ }
+
+ private Dictionary>> ReadXmlToNestedDictionary(XDocument doc)
+ {
+ var result = new Dictionary>>();
+ var mainDevice = doc.Root.Element("Device");
+ string mainDeviceName = mainDevice.Attribute("name").Value;
+ result[mainDeviceName] = new Dictionary>();
+
+ foreach (var subDevice in mainDevice.Elements("Device"))
+ {
+ string subDeviceName = subDevice.Attribute("name").Value;
+ result[mainDeviceName][subDeviceName] = new Dictionary();
+
+ foreach (var type in subDevice.Elements("Type"))
+ {
+ string typeName = type.Attribute("name").Value;
+ string typeValue = type.Value;
+ result[mainDeviceName][subDeviceName][typeName] = typeValue;
+ }
+ }
+
+ return result;
+ }
+
+ public void PrintStructure()
+ {
+ foreach (var mainDevice in deviceData)
+ {
+ Console.WriteLine($"Main Device: {mainDevice.Key}");
+ foreach (var subDevice in mainDevice.Value)
+ {
+ Console.WriteLine($" Sub Device: {subDevice.Key}");
+ foreach (var type in subDevice.Value)
+ {
+ Console.WriteLine($" Type: {type.Key}, Value: {type.Value}");
+ }
+ }
+ }
+ }
+
+ public bool TryGetValue(string mainDeviceName, string subDeviceName, string typeName, out string value)
+ {
+ value = null;
+ return deviceData.TryGetValue(mainDeviceName, out var mainDevice) &&
+ mainDevice.TryGetValue(subDeviceName, out var subDevice) &&
+ subDevice.TryGetValue(typeName, out value);
+ }
+
+ public Dictionary>> GetAllData()
+ {
+ return deviceData;
+ }
+ }
+}
diff --git a/IOT.Services/log4net.config b/IOT.Services/log4net.config
new file mode 100644
index 0000000..b177cf0
--- /dev/null
+++ b/IOT.Services/log4net.config
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/IOT.Services/packages.config b/IOT.Services/packages.config
new file mode 100644
index 0000000..92e2b4b
--- /dev/null
+++ b/IOT.Services/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/JinYuan.ControlCenter/ControlCenter.cs b/JinYuan.ControlCenter/ControlCenter.cs
new file mode 100644
index 0000000..a1db5c1
--- /dev/null
+++ b/JinYuan.ControlCenter/ControlCenter.cs
@@ -0,0 +1,2039 @@
+using JinYuan.Helper;
+using JinYuan.MES.Models;
+using JinYuan.Models;
+using JinYuan.VirtualDataLibrary;
+using Language;
+using MiniExcelLibs;
+using PLC;
+using PLC.PLCAlarm;
+using PLCCommunication;
+using PLCCommunication.Common;
+using PLCCommunication.Common.DataConvert;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using static System.Runtime.CompilerServices.RuntimeHelpers;
+using AlarmData = JinYuan.Models.AlarmData;
+using DataType = PLC.DataType;
+
+
+namespace JinYuan.ControlCenters
+{
+ public partial class ControlCenter : IDisposable
+ {
+ #region 1、变量定义
+ private static readonly object uiLock = new object();
+ private static ControlCenter _instance;
+ public static ControlCenter Instance
+ {
+ get
+ {
+ lock (uiLock)
+ {
+ return _instance ??= new ControlCenter();
+ }
+ }
+ }
+
+ private readonly Dictionary groups = new Dictionary();
+ private Dictionary, PlcGroup, Task>> stationFunctions;
+ //private Dictionary, PlcGroup>> stationFunctions;
+ private readonly Dictionary> plcDeviceMapping = new Dictionary>();
+ private readonly Dictionary groupTasks = new Dictionary();
+ private readonly Dictionary plcAlarmReaders = new Dictionary();
+
+ public CancellationTokenSource _cts;
+ private Task _heartbeatTask;
+ private const int HeartbeatInterval = 1000; // 1 second
+
+ private Dictionary group_status = new Dictionary();
+
+ ///
+ /// 定时器-定时刷新状态和数据
+ ///
+ private System.Timers.Timer Timer_RefreshData;
+
+ private readonly string configFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config");
+ #endregion
+
+ #region 2、初始化函数
+ ///
+ /// 构造函数
+ ///
+ private ControlCenter()
+ {
+ _cts = new CancellationTokenSource();
+
+ if (CommonMethods.plcDevices == null)
+ {
+ CommonMethods.plcDevices = new List();
+ }
+ InitializeDevicesFromExcel();
+ GetAlarmData();
+ InitFunc();
+ //initPrint();//打印机初始化
+ Timer_RefreshData = new System.Timers.Timer(1000);
+ Timer_RefreshData.Elapsed += Timer_RefreshData_Elapsed;
+ Timer_RefreshData.Start();
+
+ // 轴位置监控
+ //Task.Run(() => { ShowAxleEven_Run(); });
+
+ // 电机位置监控
+ //Task.Run(() => { ShowMotorEven_Run(); });
+
+ //Mes心跳监控
+ Task.Run(new Action(() => { UpdateMesHeartBeat(); }));
+
+ //删除30天内日志
+ //DeleteLog.Start(CommonMethods.strDeletepath);//会把整个ApiLog删除
+ string[] clrPath = new string[] { CommonMethods.strAlarmLogspath, CommonMethods.strMesLogspath, CommonMethods.strWattrMeterpath, Application.StartupPath + "\\Logs\\" };
+ short[] seltWay = new short[] { 0, 0, 0, 1 };
+ DeleteLog.Start(clrPath, seltWay);
+
+ if (!CommonMethods.IsRun)
+ {
+ CommonMethods.ResetEventRecv.Reset();
+ }
+ }
+
+ ///
+ /// 初始化 工位方法,添加到字典里面
+ ///
+ private void InitFunc()
+ {
+ stationFunctions = new Dictionary, PlcGroup, Task>>
+ {
+ ["包装箱进站"] = FeedingStation, // 线头扫描,包装箱进站
+ ["中部扫描"] = FeedingStationB, // 中部扫描
+ ["尾部扫描"] = FeedingStationC, // 尾部扫描
+ //["箱唛标签打印"] = PrintContainerStation, // 箱唛打印
+ //["栈板标签打印"] = PrintPalletStation, // 栈板打印,O2不打印
+ //["3S标签打印"] = Print3SStation, // 3S打印 在线网站拿数据的,贴标打印机自行打印
+ //["6J标签打印"] = Print6JStation, // 6J打印
+ //["绑定客户标签"] = BindingLabelStation, // 绑定客户标签
+ ["包装箱出站"] = BlankingStation, // 包装箱出站
+ ["中转数据"] = TransferStation, // 中转数据
+ };
+ }
+
+ ///
+ /// 启动心跳及工位读取线程
+ ///
+ ///
+ public async Task StartAsync()
+ {
+ CreatePLCThreads();
+ _heartbeatTask = StartHeartbeatCheck(_cts.Token);
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// 加载配置文件
+ ///
+ private void InitializeDevicesFromExcel()
+ {
+ CommonMethods.AddDataMonitorLog(1, "开始加载配置参数……".Translated());
+ string deviceConfigPath = Path.Combine(configFolderPath, "Device.xlsx");
+ string groupConfigPath = Path.Combine(configFolderPath, "Group.xlsx");
+ string variableConfigPath = Path.Combine(configFolderPath, "Variable.xlsx");
+ //string plcVariableConfigPath = Path.Combine(configFolderPath, "PLCVariable.xlsx");
+
+ if (!File.Exists(deviceConfigPath) || !File.Exists(groupConfigPath) || !File.Exists(variableConfigPath))
+ {
+ LogHelper.Instance.WriteError("配置文件不存在");
+ return;
+ }
+
+ try
+ {
+ var deviceConfigs = MiniExcel.Query(deviceConfigPath).ToList();
+ var groupConfigs = MiniExcel.Query(groupConfigPath).ToList();
+ var variableConfigs = MiniExcel.Query(variableConfigPath).ToList();
+
+ foreach (var deviceConfig in deviceConfigs.Where(d => d.IsActive))
+ {
+ IPEndPoint iep = new IPEndPoint(IPAddress.Parse(deviceConfig.IPAddress), deviceConfig.Port);
+ PlcReadWriteBase plc = PLCFactory.CreatePLC(GetPLCTypeEnum(deviceConfig.PLCType), deviceConfig.PlcNum, $"PLC_{deviceConfig.PlcNum}", iep);
+
+ deviceConfig.Cts = new CancellationTokenSource();
+ deviceConfig.GroupList = groupConfigs
+ .Where(g => g.IsActive && g.PlcNum == deviceConfig.PlcNum)
+ .ToList();
+
+ foreach (var group in deviceConfig.GroupList)
+ {
+ group.VarList = variableConfigs
+ .Where(v => v.GroupName == group.GroupName && v.PlcNum == deviceConfig.PlcNum)
+ .ToList();
+
+ group_status[group.GroupName] = 0;
+ }
+
+ plcDeviceMapping[plc] = new List { deviceConfig };
+ CommonMethods.plcDevices.Add(plc);
+
+ // 初始化 PLCAlarm Reader报警读取
+ var alarmReader = new PLCAlarmReader(
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AlarmConfig.csv"),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AlarmRecords"),
+ RecordType.RecordWhenFinish,
+ false
+ );
+
+ //alarmReader.PLCAlarm_New += AlrmReader_PLCAlarm_New;
+ //alarmReader.PLCAlarm_Remove += AlrmReader_PLCAlarm_Remove;
+ plcAlarmReaders[plc] = alarmReader;
+
+ CommonMethods.plcStatuses[plc] = new Dictionary
+ {
+ ["OldStatus"] = (short)0,
+ ["StatusCount"] = 0,
+ ["GuidAlarm"] = string.Empty,
+ ["Status"] = new List { false, false, false, false }
+ };
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"加载配置失败:{ex.Message}");
+ }
+ CommonMethods.AddDataMonitorLog(1, "配置文件加载完成".Translated());
+ }
+
+ #endregion
+
+ #region 3、PLC读写
+
+ #region 创建PLC线程 几个工位就几个线程
+ ///
+ /// 按工位读取线程
+ ///
+ ///
+ private readonly Dictionary groupThreads = new Dictionary();
+ private readonly object lockObject = new object();
+ private void CreatePLCThreads()
+ {
+ for (int i = 0; i < CommonMethods.plcDevices.Count; i++)
+ {
+ var plc = CommonMethods.plcDevices[i];
+ if (plcDeviceMapping.TryGetValue(plc, out var plcDevices))
+ {
+ foreach (var plcDevice in plcDevices)
+ {
+ foreach (var plcGroup in plcDevice.GroupList.Where(g => g.IsActive))
+ {
+ string threadKey = $"{plcDevice.PlcNum}-{plcGroup.GroupName}";
+
+ var parameter = (group: plcGroup, plcIndex: i);
+
+ Thread thread = new Thread(new ParameterizedThreadStart(ReadPLCWorkModel));
+ thread.IsBackground = true;
+ thread.Priority = ThreadPriority.Highest;
+ lock (lockObject)
+ {
+ groupThreads[threadKey] = thread;
+ }
+ thread.Start(parameter);
+ }
+ }
+ }
+ }
+ }
+ #endregion
+
+ #region 循环读取PLC工位状态
+ ///
+ /// 读取PLC循环工位
+ ///
+ ///
+ ///
+ private void ReadPLCWorkModel(object parameter)
+ {
+ if (parameter is ValueTuple tuple)
+ {
+ var (group, plcIndex) = tuple;
+
+ while (!_cts.Token.IsCancellationRequested)
+ {
+ if (CommonMethods.IsRun)
+ {
+ if (CommonMethods.plcDevices[plcIndex].IsConnected)
+ {
+ try
+ {
+ JYResult OpenValue = CommonMethods.plcDevices[plcIndex].ReadValue>(group.VarList[0].VarAddress, DataType.Short);
+ if (OpenValue.IsSuccess && OpenValue.Content == 1 && group_status[group.GroupName] == 0)
+ {
+ group_status[group.GroupName] = 1;
+ JYResult resByte = CommonMethods.plcDevices[plcIndex].ReadValue>($"{group.VarList[2].VarAddress}",
+ DataType.ArrByte, Convert.ToUInt16(group.VarList[2].OffsetOrLength));
+
+ if (resByte != null && stationFunctions.TryGetValue(group.GroupName, out var action))
+ {
+ Task.Run(() => action(resByte, group));
+ }
+ }
+ else if (OpenValue.IsSuccess && OpenValue.Content == 0) // 触发地址被PLC清0后的处理处理
+ {
+ if(group_status[group.GroupName] != 0)
+ {
+ group_status[group.GroupName] = 0;
+ CommonMethods.AddDataMonitorLog(0, $"{group.GroupName} PLC设置为0,清空状态为0");
+ }
+ JYResult checkValue = CommonMethods.plcDevices[plcIndex].ReadValue>(group.VarList[1].VarAddress, DataType.Short);
+ if (checkValue.IsSuccess && checkValue.Content != 0)
+ {
+ CommonMethods.plcDevices[plcIndex].WriteValue(group.VarList[1].VarAddress, 0, DataType.Short);
+ LogHelper.Instance.WriteLog($"记录: {group.VarList[1].VarAddress}清0");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ group_status[group.GroupName] = 0;
+ LogHelper.Instance.WriteError($"错误: {group.GroupName} - {ex.Message}");
+ }
+ }
+ }
+ Thread.Sleep(50);
+ }
+ }
+ }
+ #endregion
+
+ #region 开始停止联机事件
+ ///
+ /// 开始联机
+ ///
+ ///
+
+ public async Task ConnectAllPlcsAsync()
+ {
+ bool allConnected = true;
+ if (CommonMethods.plcDevices.Count <= 0)
+ {
+ CommonMethods.AddDataMonitorLog(1, "无PLC配置信息".Translated() + "," + "请检查配置文件".Translated());
+ return allConnected = false;
+ }
+ foreach (var plc in CommonMethods.plcDevices)
+ {
+ if (plcDeviceMapping.TryGetValue(plc, out var plcDevices))
+ {
+ foreach (var deviceConfig in plcDevices)
+ {
+ string msg = "";
+ try
+ {
+ // 使用 Task.Run 来在后台线程执行连接操作
+ bool isConnected = await Task.Run(() =>
+ {
+ // 设置一个连接超时
+ using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) // 10秒超时
+ {
+ try
+ {
+ return plc.Connect(ref msg);
+ }
+ catch (OperationCanceledException)
+ {
+ msg = "连接超时";
+ return false;
+ }
+ }
+ }).ConfigureAwait(false);
+
+ deviceConfig.IsConnected = isConnected;
+
+ if (!deviceConfig.IsConnected)
+ {
+ allConnected = false;
+ LogHelper.Instance.WriteLog($"连接PLC失败: {deviceConfig.PlcNum}, 错误: {msg}");
+ CommonMethods.AddDataMonitorLog(1, "连接PLC失败".Translated() + $": {deviceConfig.PlcNum}, " + "错误".Translated() + $": {msg}");
+ }
+ else
+ {
+ await SendRightToPLCAsync();
+ CommonMethods.AddDataMonitorLog(0, "成功连接PLC".Translated() + $": {deviceConfig.PlcNum}");
+ }
+ }
+ catch (Exception ex)
+ {
+ allConnected = false;
+ LogHelper.Instance.WriteLog($"连接PLC时发生异常: {deviceConfig.PlcNum}, 错误: {ex.Message}");
+ CommonMethods.AddDataMonitorLog(2, "连接PLC时发生异常".Translated() + $": {deviceConfig.PlcNum}" + "错误".Translated() + $": {ex.Message}");
+ }
+ }
+ }
+ }
+ return allConnected;
+ }
+
+ ///
+ /// 断开链接
+ ///
+ public void DisconnectAllPlcs()
+ {
+ foreach (var plc in CommonMethods.plcDevices)
+ {
+ if (plcDeviceMapping.TryGetValue(plc, out var plcDevices))
+ {
+ foreach (var deviceConfig in plcDevices)
+ {
+ plc.Disconnect();
+ deviceConfig.IsConnected = false;
+ CommonMethods.AddDataMonitorLog(0, "已断开与PLC".Translated() + $"[{deviceConfig.PlcNum}]" + "的链接".Translated());
+ }
+ }
+ }
+ }
+ #endregion
+ #endregion
+
+ #region MES
+ ///
+ /// Mes心跳
+ ///
+ private void UpdateMesHeartBeat()
+ {
+ Thread.Sleep(100);
+ while (true)//true
+ {
+ try
+ {
+ if (CommonMethods.mesConfig.IsLoginMesOK && CommonMethods.IsRun)//CommonMethods.mesConfig.isUpMes &&
+ {
+ if (Ping() == true)
+ {
+ CommonMethods.mesConfig.isConnected = true;
+ }
+ else//Ping失败,提示检查线路。
+ {
+ CommonMethods.mesConfig.isConnected = false;
+ if (DateTime.Now.Second % 10 == 0)
+ {
+ string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping失败,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort}";
+ TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping异常,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort},原因:{ex.ToString()}";
+ TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
+ }
+ Thread.Sleep(1000);
+ }
+ }
+
+ ///
+ /// Ping 检查网络线路问题
+ ///
+ ///
+ public bool Ping()
+ {
+ try
+ {
+ string strIP = CommonMethods.mesConfig.MESURLAddressIP;
+ int port = Convert.ToInt32(CommonMethods.mesConfig.MESURLAddressPort);
+ IPAddress ip = IPAddress.Parse(strIP);
+ IPEndPoint _endPoint = new IPEndPoint(ip, port);
+
+ if (_endPoint.Address == null)
+ {
+ return false;
+ }
+
+ Ping _ping = new Ping();
+ int timeout = 1000;
+ PingReply mPingRcv = _ping.Send(_endPoint.Address, timeout);
+ return (mPingRcv.Status == IPStatus.Success) ? true : false;
+ }
+ catch (Exception ex)
+ {
+ string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping异常,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort},原因:{ex.ToString()}";
+ TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr);
+ return false;
+ }
+
+ }
+ #endregion
+
+ #region 4、心跳和状态检查
+ ///
+ /// 心跳检测
+ ///
+ ///
+ ///
+ private async Task StartHeartbeatCheck(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ if (CommonMethods.IsRun)
+ {
+ foreach (var plc in CommonMethods.plcDevices)
+ {
+ if (plcDeviceMapping.TryGetValue(plc, out var plcDevices))
+ {
+ foreach (var plcDevice in plcDevices)
+ {
+ if (plcDevice.IsActive && plcDevice.IsHeartBeat)
+ {
+ await CheckHeartbeatForDevice(plc, plcDevice);
+ //await ShowStateEven_RunAsync();
+ }
+ }
+ }
+ }
+ }
+ await Task.Delay(HeartbeatInterval, cancellationToken);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task CheckHeartbeatForDevice(PlcReadWriteBase plc, PlcDevice plcDevice)
+ {
+ if (plcDevice.IsConnected)
+ {
+ JYResult result = await Task.Run(() => plc.ReadValue>(plcDevice.HeartBeat, DataType.Short));
+ if (result.IsSuccess)
+ {
+ plcDevice.ErrorTimes = 0;
+ JYResult res = await Task.Run(() => plc.WriteValue(plcDevice.HeartBeat, (short)1, DataType.Short));
+ if (!res.IsSuccess)
+ {
+ plcDevice.IsConnected = false;
+ LogHelper.Instance.WriteLog($"PLC编号:{plcDevice.PlcNum}=心跳写入异常:{res.Message}!!!");
+ }
+ }
+ else
+ {
+ HandleHeartbeatFailure(plcDevice, result);
+ }
+ }
+ else
+ {
+ await ReconnectDevice(plc, plcDevice);
+ }
+ }
+
+ private void HandleHeartbeatFailure(PlcDevice plcDevice, JYResult result)
+ {
+ plcDevice.ErrorTimes++;
+ plcDevice.IsConnected = false;
+ if (plcDevice.ErrorTimes >= plcDevice.AllowErrorTimes)
+ {
+ LogHelper.Instance.WriteLog($"编号:[{plcDevice.PlcNum}]的PLC链接断开:{result.Message}");
+ }
+ }
+
+ private async Task ReconnectDevice(PlcReadWriteBase plc, PlcDevice plcDevice)
+ {
+ if (!plcDevice.IsFirstConnect)
+ {
+ await Task.Delay(plcDevice.ReConnectTime);
+ plc.Disconnect();
+ }
+ string msg = "";
+ var connectResult = await Task.Run(() => plc.Connect(ref msg));
+ plcDevice.IsConnected = connectResult;
+ LogHelper.Instance.WriteLog(connectResult
+ ? $"PLC编号:{plcDevice.PlcNum}{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接成功{msg}!"
+ : $"[{plcDevice.PlcNum}]#PLC{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接失败{msg}!");
+
+ plcDevice.IsFirstConnect = false;
+ }
+
+ #endregion
+
+ #region 5、报警处理 ×
+ //private async Task CheckAlarmsAsync(PlcReadWriteBase plc, string guidAlarm)
+ //{
+ // if (plc == null)
+ // {
+ // CommonMethods.AddDataMonitorLog(2, "PLC 对象为 null".Translated());
+ // return;
+ // }
+
+ // if (!plc.IsConnected)
+ // {
+ // CommonMethods.AddDataMonitorLog(2, "PLC 未连接".Translated());
+ // return;
+ // }
+
+ // if (plcAlarmReaders.TryGetValue(plc, out var alarmReader))
+ // {
+ // try
+ // {
+ // JYResult result = null;
+
+ // // 使用 Task.Run 和超时机制
+ // var task = Task.Run(() =>
+ // {
+ // try
+ // {
+ // return plc.ReadValue>("D10000", DataType.ArrUshort, 20);
+ // }
+ // catch (Exception ex)
+ // {
+ // CommonMethods.AddDataMonitorLog(2, "PLC 读取过程中发生异常".Translated() + $": {ex.Message}");
+ // return null;
+ // }
+ // });
+
+ // // 设置5秒超时
+ // if (await Task.WhenAny(task, Task.Delay(5000)) == task)
+ // {
+ // result = await task;
+ // }
+ // else
+ // {
+ // throw new TimeoutException("PLC 读取操作超时");
+ // }
+
+ // if (result == null)
+ // {
+ // throw new InvalidOperationException("PLC 读取操作返回空结果");
+ // }
+
+ // if (!result.IsSuccess)
+ // {
+ // throw new InvalidOperationException($"PLC 读取操作失败: {result.Message}");
+ // }
+
+ // if (result.Content == null)
+ // {
+ // throw new InvalidOperationException("PLC 读取操作返回空内容");
+ // }
+
+ // ushort[] alarmData = result.Content;
+
+ // // 使用 alarmData 进行后续操作
+ // bool isAlarming = alarmReader.IsPLCAlarm(10000, 20, alarmData);
+
+ // if (isAlarming)
+ // {
+ // foreach (var alarm in alarmReader.RecordAlarms)
+ // {
+ // CommonMethods.AddDataMonitorLog(1, $"当前报警: 代码 {alarm.AlarmCode}, 内容 {alarm.AlarmInfo}, 开始时间 {alarm.StartTime}");
+ // }
+ // }
+ // }
+ // catch (Exception ex)
+ // {
+ // CommonMethods.AddDataMonitorLog(2, $"检查报警时发生错误: {ex.Message}");
+ // }
+ // }
+ // else
+ // {
+ // CommonMethods.AddDataMonitorLog(2, $"未找到 PLC 的报警读取器");
+ // }
+ //}
+
+ ///
+ /// 新增报警
+ ///
+ ///
+ ///
+ //private void AlrmReader_PLCAlarm_New(object sender, RecordAlarmDataArgs e)
+ //{
+ // Task.Run(() =>
+ // {
+ // CommonMethods.alarmManager.AddAlarm(e);
+ // Debug.WriteLine($"新报警: 代码 {e.AlarmCode}, 内容 {e.AlarmInfo}, 日期 {e.AlarmDate}, 开始时间 {e.StartTime:HH:mm:ss}");
+ // });
+ //}
+
+ ///
+ /// 报警结束移除报警
+ ///
+ ///
+ ///
+ //private void AlrmReader_PLCAlarm_Remove(object sender, RecordAlarmDataArgs e)
+ //{
+ // Task.Run(() =>
+ // {
+ // CommonMethods.alarmManager.RemoveAlarm(e);
+ // Debug.WriteLine($"报警清除: 代码 {e.AlarmCode}, 内容 {e.AlarmInfo}, 日期 {e.AlarmDate}, 结束时间 {DateTime.Now:HH:mm:ss}");
+ // });
+ //}
+
+ #endregion
+
+ #region 6、设备状态检查 ×
+
+ ///
+ /// 设备状态
+ ///
+ ///
+ //private async Task ShowStateEven_RunAsync()
+ //{
+ // try
+ // {
+ // string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+ // foreach (var plc in CommonMethods.plcDevices)
+ // {
+ // if (plcDeviceMapping.TryGetValue(plc, out var plcDevices))
+ // {
+ // foreach (var plcDevice in plcDevices)
+ // {
+ // if (plcDevice.IsConnected)
+ // {
+ // var plcStatus = CommonMethods.plcStatuses[plc];
+ // bool isUpload = false;
+ // string deviceStatus = string.Empty;
+ // string deviceStatusDesc = string.Empty;
+ // short newStatus = 100;
+ // newStatus = await Task.Run(() => plc.ReadValue>("D10", DataType.Short).Content);
+
+ // if (newStatus != 100)
+ // {
+ // if (newStatus != (short)plcStatus["OldStatus"])
+ // {
+ // // 从 Alert 切换到非 Alert 状态时,上传报警信息
+ // if ((short)plcStatus["OldStatus"] == 3 && newStatus != 3)
+ // {
+ // await CheckAlarmsAsync(plc, (string)plcStatus["GuidAlarm"]);
+ // }
+
+ // // 只要状态变更,就生成新的 GUID
+ // plcStatus["GuidAlarm"] = Guid.NewGuid().ToString();
+
+ // isUpload = true;
+ // plcStatus["StatusCount"] = 0;
+ // plcStatus["OldStatus"] = newStatus;
+ // }
+ // else
+ // {
+ // plcStatus["StatusCount"] = (int)plcStatus["StatusCount"] + 1;
+ // if ((int)plcStatus["StatusCount"] >= 5 * 60)
+ // {
+ // isUpload = true;
+ // plcStatus["StatusCount"] = 0;
+ // }
+ // }
+
+ // (deviceStatus, deviceStatusDesc) = newStatus switch
+ // {
+ // 1 => ("Run", "正常运行"),
+ // 2 => ("Maintain", "停止运行"),
+ // 3 => ("Alert", "故障调试"),
+ // 4 => ("Idle", "闲置待机"),
+ // _ => (string.Empty, string.Empty)
+ // };
+
+ // plcStatus["Status"] = new List { newStatus == 1, newStatus == 2, newStatus == 3, newStatus == 4 };
+ // CommonMethods.Statues = new List { newStatus == 1, newStatus == 2, newStatus == 3, newStatus == 4 };
+ // if (newStatus == 3) //报警状态
+ // {
+ // await CheckAlarmsAsync(plc, (string)plcStatus["GuidAlarm"]);
+ // }
+ // else if (newStatus == 4) //闲置状态
+ // {
+ // await ShowStuesEven_Run(Date, (string)plcStatus["GuidAlarm"], plc);
+ // }
+
+ // if (isUpload)
+ // {
+ // CommonMethods.AddDataMonitorLog(1, $"PLC[{plcDevice.PlcNum}]状态: {deviceStatusDesc}");
+ // }
+ // if (CommonMethods.sysConfig.MesModeSwitching)
+ // {
+ // // 上传设备状态信息
+ // if (CommonMethods.sysConfig.MesModeSwitching && isUpload && (newStatus == 1 || newStatus == 3 || newStatus == 4))
+ // {
+ // var (success, message) = await CommonMethods.hbgMes.UploadEquStatusAsync(
+ // CommonMethods.mesConfig.DeviceStatus,
+ // CommonMethods.mesConfig.siteCode,
+ // CommonMethods.mesConfig.lineCode,
+ // CommonMethods.mesConfig.equipNum,
+ // CommonMethods.mesConfig.MaterialCode,
+ // CommonMethods.mesConfig.mesUserName,
+ // strGuidAlarm, // RUN 状态时传空
+ // deviceStatus,
+ // null
+ // );
+
+ // if (success)
+ // {
+ // StatuesCount = 0;
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+ // catch (Exception ex)
+ // {
+ // await Task.Run(() => LogHelper.Instance.WriteEX(ex));
+ // }
+ //}
+
+
+ ///
+ /// HMI手动触摸屏状态
+ ///
+ ///
+ ///
+ ///
+ ///
+ //private async Task ShowStuesEven_Run(string Date, string strGuid, PlcReadWriteBase plc)
+ //{
+ // if (plc.IsConnected)
+ // {
+ // string strCode = "";
+ // string strText = "";
+ // short HMIStatus = await Task.Run(() => plc.ReadValue>("D40", DataType.Short).Content);
+
+ // if (HMIStatus != 0)
+ // {
+ // CommonMethods.AddDataMonitorLog(1, "触发了手动HMI弹窗功能!");
+ // (strCode, strText) = HMIStatus switch
+ // {
+ // 1 => ("B1", "计划维保"),
+ // 2 => ("B2", "无生产计划"),
+ // 3 => ("B3", "样品、试产"),
+ // 4 => ("B4", "其他计划停机"),
+ // 5 => ("D1", "设备故障"),
+ // 6 => ("D2", "设备转型"),
+ // 7 => ("D3", "工程变更"),
+ // 8 => ("D4", "MES停机"),
+ // 9 => ("D5", "品质异常"),
+ // 10 => ("D6", "配件、物料更换"),
+ // 11 => ("D7", "来料不良"),
+ // 12 => ("D8", "待料"),
+ // 13 => ("D9", "品质检查"),
+ // 14 => ("D10", "清洁点检"),
+ // 15 => ("D11", "环境不满足"),
+ // 16 => ("D12", "其它非计划停机"),
+ // _ => (string.Empty, string.Empty)
+ // };
+
+ // if (!string.IsNullOrEmpty(strCode))
+ // {
+ // CommonMethods.AddDataMonitorLog(1, $"{strCode} {strText}");
+ // }
+
+
+ // if (CommonMethods.sysConfig.MesModeSwitching)
+ // {
+ // List alarmObj = new List();
+ // AlarmData alarmData = new AlarmData();
+ // alarmData.AlarmCode = strCode;
+ // alarmData.StartTime = "";
+ // alarmData.EndTime = "";
+ // alarmData.AlarmType = "非计划损失";
+ // alarmData.AlarmContent = strText;
+ // alarmObj.Add(alarmData);
+ // //上传报警
+ // var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode,
+ // CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, alarmObj, strGuid);
+ // if (success)
+ // CommonMethods.AddDataMonitorLog(0, $"HMI手动弹窗状态已上传成功,代码:{strCode}");
+ // }
+ // bool IsSuccess = CommonMethods.plcDevices[0].WriteInt16("D40", 0);
+ // if (IsSuccess)
+ // CommonMethods.AddDataMonitorLog(0, $"D40已复位0");
+
+ // }
+ // }
+ //}
+
+ #endregion
+
+ #region 定时刷新数据
+
+ int TotalNum = 0;
+ int TotalNum1 = 0;
+ int Regular = 0;
+ int JetBeat = 0;
+ private void Timer_RefreshData_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+ {
+ if (Regular >= 300)
+ {
+ Task.Run(() =>
+ {
+ UploadEquEnergy();//能耗
+ //ReUpProducResultParam();//定时补传
+ });
+ Regular = 0;
+ }
+ if (TotalNum > 4)
+ {
+ GetEnergyData();
+ //GetEnergyDataFormPlc();
+ Refresh12HourProdQty();
+ GetDefectTypeQty();
+ ReadPLCComm();
+
+ GetASqlAlarmData(DateTime.Today, CommonMethods.strClass);
+ TotalNum = 0;
+ }
+ else if (TotalNum == 2)//设备状态读取
+ {
+ SetClass(); //变更班次
+ }
+
+ Task.Run(() =>
+ {
+ ShowStateEven_Run();
+ });
+
+ if (TotalNum1 >= 3600 * 24)
+ {
+ Task.Run(() =>
+ {
+ DeleteFileOfLocal();
+ });
+ TotalNum1 = 0;
+ }
+
+ //电能表心跳
+ //if (jetBeat > 5)
+ //{
+ // if (!CommonMethods.energyMeter.Connected)
+ // {
+ // CommonMethods.energyMeter.SetTCPParams(IPAddress.Parse("192.168.1.25"), 502);
+ // CommonMethods.energyMeter.Connect();
+ // }
+ // jetBeat = 0;
+ //}
+
+ //jetBeat++;
+ TotalNum++;
+ TotalNum1++;
+ Regular++;
+ }
+ #endregion
+
+ #region 设备综合信息
+ ///
+ /// 读取PLC通用信息
+ ///
+ internal void ReadPLCComm()
+ {
+ if (CommonMethods.plcDevices.Count != 0 && CommonMethods.plcDevices[0].IsConnected)
+ {
+ Task.Run(() =>
+ {
+ UpTotalCount(null);
+ });
+
+ CommonMethods.TotalQty = CommonMethods.plcDevices[0].ReadFloat("D15");//生产总数
+ CommonMethods.ProOKQty = CommonMethods.plcDevices[0].ReadFloat("D17");//良品总数
+
+ CommonMethods.Ppm = CommonMethods.plcDevices[0].ReadFloat("D13");
+ CommonMethods.JiaDL = Convert.ToDouble(CommonMethods.plcDevices[0].ReadFloat("D11").ToString("F2"));
+
+ #region 开机时间/运行时间
+ CommonMethods.PlcOpenAndRunTime = GetList(6, 0);
+ JYResult resByte = CommonMethods.plcDevices[0].ReadValue>($"D50", DataType.ArrByte, 12);
+ if (resByte.IsSuccess && resByte.Content.Length >= 24)
+ {
+ for (int i = 0; i < CommonMethods.PlcOpenAndRunTimeAdress.Count; i++)
+ {
+ CommonMethods.PlcOpenAndRunTime[i] = IntLib.GetIntFromByteArray(resByte.Content, i * 4, DataFormat.CDAB);//ShortLib.GetShortFromByteArray(resByte.Content, i * 4, DataFormat.CDAB);
+ }
+ }
+ #endregion
+ }
+
+ }
+ #endregion
+
+ #region 保存12小时产能数据
+ public int totalNum = 0; // 总数
+ public int okNum = 0; // 良品数
+
+ ///
+ /// 储存数据到数据库
+ ///
+ ///
+ public void UpTotalCount(int[] DataCount)
+ {
+
+ try
+ {
+ DateTime dateTime = DateTime.Now;
+ string strDate = dateTime.ToString("yyyy-MM-dd");
+
+ // 获取当前时间的小时和分钟
+ int currentHour = dateTime.Hour;
+ int currentMinute = dateTime.Minute;
+
+ // 根据当前分钟确定统计小时
+ // 如果当前时间在 X:30 之前,则统计的是前一个小时的数据(从前一个小时的30分到这个小时的29分)
+ // 如果当前时间在 X:30 之后,则统计的是当前小时的数据(从当前小时的30分到下个小时的29分)
+ int statisticalHour;
+ if (currentMinute < 30)
+ {
+ statisticalHour = (currentHour + 23) % 24; // 前一个小时,处理跨天的情况
+ // 如果是凌晨0点前的数据,需要使用前一天的日期
+ if (currentHour == 0)
+ {
+ strDate = dateTime.AddDays(-1).ToString("yyyy-MM-dd");
+ }
+ }
+ else
+ {
+ statisticalHour = currentHour;
+ }
+
+ // 数据有效性验证
+ if (statisticalHour < 0 || statisticalHour > 23)
+ {
+ throw new ArgumentException($"无效的小时值: {statisticalHour}");
+ }
+
+ //读取统计小时的PLC投入总数和产出数
+ int ProdCurrAllQty = GetToDayHourQty(statisticalHour, true);
+ int ProdCurrOKQty = GetToDayHourQty(statisticalHour, false);
+
+ // 确保数量为非负数
+ if (ProdCurrAllQty < 0 || ProdCurrOKQty < 0)
+ {
+ LogHelper.Instance.WriteError($"产量不能为负数: 投入={ProdCurrAllQty}, 产出={ProdCurrOKQty}");
+ //throw new ArgumentException($"产量不能为负数: 投入={ProdCurrAllQty}, 产出={ProdCurrOKQty}");
+ }
+
+ Hourprod mh = new Hourprod
+ {
+ FDate = strDate,
+ FHour = statisticalHour,
+ ProdIn = ProdCurrAllQty,
+ ProdOut = ProdCurrOKQty,
+ TestTime = dateTime,
+ Ppm = (int)CommonMethods.Ppm,
+ JiaDL = Convert.ToDecimal(CommonMethods.JiaDL)
+ };
+
+ // 尝试更新记录
+ int res = CommonMethods.db.UpdateSingle(
+ mh,
+ it => new Hourprod
+ {
+ ProdIn = mh.ProdIn,
+ ProdOut = mh.ProdOut,
+ TestTime = mh.TestTime,
+ Ppm = mh.Ppm,
+ JiaDL = mh.JiaDL
+ },
+ it => it.FDate == mh.FDate && it.FHour == mh.FHour
+ );
+
+ // 如果更新失败(记录不存在),则新增记录
+ if (res <= 0)
+ {
+ bool addResult = CommonMethods.db.AddReturnBool(mh);
+ if (!addResult)
+ {
+ LogHelper.Instance.WriteError($"保存小时产能新增记录失败");
+ }
+ }
+
+ // 可以添加日志记录成功信息
+ // Logger.Log($"成功更新/新增产能数据: 日期={strDate}, 小时={statisticalHour}, 时段={statisticalHour}:30~{(statisticalHour + 1) % 24}:29");
+ }
+ catch (Exception ex)
+ {
+ // 这里应该添加错误日志记录
+ LogHelper.Instance.WriteEX($"保存小时产能数据失败: ", ex);
+ }
+ }
+
+ ///
+ /// 每小时产能读取处理
+ ///
+ ///
+ ///
+ ///
+ private int GetToDayHourQty(int hour, bool flag)
+ {
+ try
+ {
+ string strAddr = "";
+
+ if (flag)//产出
+ {
+ strAddr = "D" + (1040 + hour);
+ }
+ else//良品数
+ {
+ strAddr = "D" + (1070 + hour);
+ }
+ int ProdNGQty = CommonMethods.plcDevices[0].ReadInt16(strAddr);
+ return ProdNGQty;
+
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message, "系统异常", MessageBoxButtons.OK, MessageBoxIcon.Hand);
+ }
+ return 0;
+ }
+
+ #endregion
+
+ #region 5、设备状态
+ ///
+ /// 定时获取设备状态
+ ///
+ ///
+ ///
+ int OldStatues = Convert.ToInt32(IniConfigHelper.ReadIniData("上一次设备状态", "DeviceStatus", "1", CommonMethods.strDeviceStatusPath));//退出程序前设备状态
+ int StatuesCount = 0;
+ DateTime StatuesBeginTime = DateTime.Now.AddMinutes(-6);
+ string strGuidAlarm = IniConfigHelper.ReadIniData("上一次Guid", "DeviceGuid", Guid.NewGuid().ToString(), CommonMethods.strDeviceStatusPath);//退出程序前Guid
+ bool run_AlarmMark = false;//特殊情况1标志(运行状态)
+ bool run_IdleMark = false;//特殊情况2标志(闲置状态)
+ bool isUpIdleAlarm = false;//闲置状态报警信息上传标志
+ List idleAlarmData = new List();//闲置状态的报警信息
+
+ private async void ShowStateEven_Run()
+ {
+ try
+ {
+ if (CommonMethods.plcDevices.Count > 0 && CommonMethods.plcDevices != null)
+ {
+ if (CommonMethods.plcDevices[0] != null && CommonMethods.plcDevices[0].IsConnected)
+ {
+ DateTime Date = DateTime.Now;
+ bool isUpload = false;
+ string deviceStatus = string.Empty;
+ string deviceStatusDesc = string.Empty;
+ short NewStatus = CommonMethods.plcDevices[0].ReadInt16("D10");
+
+ if (NewStatus != 100)
+ {
+ //特殊情况1:运行->报警->……->运行
+ if (NewStatus == 3 && OldStatues == 1) { run_AlarmMark = true; }//第一次进入报警状态
+ if (NewStatus == 1) { run_AlarmMark = false; }//下一次运行状态,置位
+ if (run_AlarmMark) { NewStatus = 3; }//报警状态后下一次运行状态期间一直是报警状态
+ //特殊情况2:运行->闲置->……->运行
+ if (NewStatus == 4 && OldStatues == 1) { run_IdleMark = true; }//第一次进入闲置状态
+ if (NewStatus == 1) { run_IdleMark = false; }//下一次闲置状态,置位
+ if (run_IdleMark) { NewStatus = 4; }//闲置状态后下一次运行状态期间一直是闲置状态
+
+ //闲置状态结束报警信息上传
+ if (OldStatues == 4 && NewStatus != 4 && isUpIdleAlarm)
+ {
+ _ = UpIdleDataAsync(idleAlarmData, strGuidAlarm);
+ }
+
+ //设备状态信号灯变量
+ CommonMethods.Statues = new List { NewStatus == 1, NewStatus == 2, NewStatus == 3, NewStatus == 4 };
+
+ // 状态切换时生成新的 GUID
+ if (NewStatus != OldStatues)
+ {
+ // 从 Alert 切换到非 Alert 状态时,上传报警信息
+ if (OldStatues == 3 && NewStatus != 3)
+ {
+ ShowAlarmEven_Run(strGuidAlarm);
+ }
+
+ // 只要状态变更,就生成新的 GUID
+ strGuidAlarm = Guid.NewGuid().ToString(); // 强制生成新的 GUID
+
+ isUpload = true;
+ //StatuesCount = 0;
+ OldStatues = NewStatus;
+ StatuesBeginTime = Convert.ToDateTime(Date);
+
+ //记录最新设备状态和Guid
+ //CommonMethods.UpdateConfig("MES_CONFIG", "DeviceStatus", NewStatus.ToString());// 保存设备状态到xml文件
+ //CommonMethods.UpdateConfig("MES_CONFIG", "Guid", strGuidAlarm);// 保存设备状态到xml文件
+
+ //CommonMethods.Configxml["MES_CONFIG"]["DeviceStatus"] = NewStatus.ToString(); // 保存设备状态到xml文件
+ //CommonMethods.Configxml["MES_CONFIG"]["Guid"] = strGuidAlarm; // 保存设备状态到xml文件
+ //CommonMethods.WriteXmlFile(CommonMethods.Configxml, CommonMethods.xmlFilePath);
+
+ IniConfigHelper.WriteIniData("上一次设备状态", "DeviceStatus", NewStatus.ToString(), CommonMethods.strDeviceStatusPath);
+ IniConfigHelper.WriteIniData("上一次Guid", "DeviceGuid", strGuidAlarm, CommonMethods.strDeviceStatusPath);
+ }
+ else
+ {
+ // 同种状态持续 5 分钟时上传状态信息
+ //StatuesCount++;
+ //if (StatuesCount >= 5 * 60)
+ //{
+ // isUpload = true;
+ // StatuesCount = 0;
+ //}
+ if (Date.Subtract(StatuesBeginTime).TotalSeconds >= 5 * 60)
+ {
+ isUpload = true;
+ StatuesBeginTime = DateTime.Now;
+ }
+ }
+
+ // 根据状态设置设备状态和描述
+ switch (NewStatus)
+ {
+ case 1:
+ deviceStatus = "Run";
+ deviceStatusDesc = "正常运行";
+ break;
+ case 2://取消
+ deviceStatus = "Maintain";
+ deviceStatusDesc = "停止运行";
+ break;
+ case 3:
+ deviceStatus = "Alert";
+ deviceStatusDesc = "故障调试";
+ // Alert 状态时上传报警信息
+ ShowAlarmEven_Run(strGuidAlarm);
+ break;
+ case 4:
+ deviceStatus = "Idle";
+ deviceStatusDesc = "闲置待机";
+ ShowStuesEven_Run(strGuidAlarm);
+ break;
+ }
+
+ // 上传设备状态信息
+ if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected && isUpload && (NewStatus == 1 || NewStatus == 3 || NewStatus == 4))
+ {
+ var (success, message) = await CommonMethods.hbgMes.UploadEquStatusAsync(
+ CommonMethods.mesConfig.DeviceStatus,
+ CommonMethods.mesConfig.siteCode,
+ CommonMethods.mesConfig.lineCode,
+ CommonMethods.mesConfig.equipNum,
+ CommonMethods.mesConfig.MaterialCode,
+ CommonMethods.mesConfig.mesUserName,
+ strGuidAlarm, // RUN 状态时传空
+ deviceStatus,
+ null
+ );
+
+ if (success)
+ {
+ StatuesCount = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteEX(ex);
+ }
+ }
+
+
+ #region 触摸屏弹窗
+ private async void ShowStuesEven_Run(string strGuid)
+ {
+ if (CommonMethods.plcDevices[0].IsConnected)
+ {
+ string strCode = "";
+ string alarmName = "";
+ short HMIStatus = CommonMethods.plcDevices[0].ReadInt16("D40");
+ if (HMIStatus != 0)
+ {
+ CommonMethods.AddDataMonitorLog(0, "触发了手动HMI弹窗功能".Translated());
+ switch (HMIStatus) //注意:B1-D12都为闲置代码,上传代码是只需上传闲置代码
+ {
+ //case 1://取消
+ // strCode = "B1";
+ // alarmName = "计划维保";
+ // CommonMethods.AddDataMonitorLog(0, "B1 计划维保");
+
+ // break;
+ //case 2://取消
+ // strCode = "B2";
+ // alarmName = "无生产计划";
+ // CommonMethods.AddDataMonitorLog(0, "B2 无生产计划");
+ // break;
+ //case 3://取消
+ // strCode = "B3";
+ // alarmName = "样品、试产";
+ // CommonMethods.AddDataMonitorLog(0, "B3 样品、试产");
+ // break;
+ //case 4://取消
+ // strCode = "B4";
+ // alarmName = "其他计划停机";
+ // CommonMethods.AddDataMonitorLog(0, "B4.其他计划停机");
+ // break;
+ case 5:
+ strCode = "D1";
+ alarmName = "设备故障".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 6:
+ strCode = "D2";
+ alarmName = "设备转型".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 7:
+ strCode = "D3";
+ alarmName = "工程变更".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 8:
+ strCode = "D4";
+ alarmName = "MES停机".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 9:
+ strCode = "D5";
+ alarmName = "品质异常".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 10:
+ strCode = "D6";
+ alarmName = "配件,物料更换".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 11:
+ strCode = "D7";
+ alarmName = "来料不良".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 12:
+ strCode = "D8";
+ alarmName = "待料".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 13:
+ strCode = "D9";
+ alarmName = "品质检查".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 14:
+ strCode = "D10";
+ alarmName = "清洁点检".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 15:
+ strCode = "D11";
+ alarmName = "环境不满足".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ case 16:
+ strCode = "D12";
+ alarmName = "其它非计划停机".Translated();
+ CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}");
+ break;
+ default:
+ break;
+ }
+
+ if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected)
+ {
+ List alarmObj = new List();
+ AlarmData alarmData = new AlarmData();
+ alarmData.AlarmCode = strCode;
+ alarmData.StartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ alarmData.EndTime = "";
+ alarmData.AlarmType = "非计划损失";
+ alarmData.AlarmContent = alarmName;
+ alarmObj.Add(alarmData);
+
+ //闲置报警上传标志
+ isUpIdleAlarm = true;
+ idleAlarmData.Add(alarmData);
+
+ //上传报警
+ var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode,
+ CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, alarmObj, strGuid);
+ if (success)
+ CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗状态已上传成功,代码".Translated() + $":{strCode}");
+ }
+ bool IsSuccess = CommonMethods.plcDevices[0].WriteInt16("D40", 0);
+ if (IsSuccess)
+ CommonMethods.AddDataMonitorLog(0, $"D40已复位0".Translated());
+
+ }
+ }
+ }
+
+ #endregion
+
+ ///
+ /// HMI手动弹窗结束状态上传
+ ///
+ ///
+ ///
+ ///
+ private async Task UpIdleDataAsync(List idleAlarmDate, string strGuidAlarm)
+ {
+ if (idleAlarmDate.Count == 0) { return; }
+ string strCode = "";
+ foreach (var item in idleAlarmDate)
+ {
+ item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ strCode = item.AlarmCode + ",";
+ }
+ if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected)
+ {
+ //上传报警
+ var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode,
+ CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, idleAlarmDate, strGuidAlarm);
+ if (success)
+ CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗结束状态已上传成功,代码".Translated() + $":{strCode.TrimEnd(',')}");
+ }
+ idleAlarmData.Clear();
+ isUpIdleAlarm = false;
+ }
+
+ #endregion
+
+ #region 6、设备报警
+ //报警表单数据
+ List listAlarmForm = null;
+ public void GetAlarmData()
+ {
+ //加载PLC报警文件
+ try
+ {
+ string strFileName = System.Environment.CurrentDirectory + "\\欧姆龙报警寄存器对应表.csv";
+ listAlarmForm = CSVHelper.ReadCSV(strFileName);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("加载报警文件失败!");
+ }
+
+ }
+
+ ///
+ /// 报警信息读取
+ ///
+ ///
+ ///
+ List listOld = new List();
+ List listNew = new List();
+ List listAlarmStatus = null;
+ private async void ShowAlarmEven_Run(string strGuidAlarm)
+ {
+ if (!CommonMethods.IsRun || !CommonMethods.plcDevices[0].IsConnected)
+ {
+ return;
+ }
+
+ try
+ {
+ // 1. 读取 PLC 报警信息
+ var alarmStatusList = ReadAlarmStatusFromPLC();
+ if (alarmStatusList == null || listAlarmForm == null)//true
+ {
+ return;
+ }
+
+ // 2. 解析报警信息并生成新的报警列表
+ var newAlarms = ParseAlarmStatus(alarmStatusList, strGuidAlarm);
+ if (newAlarms.Count > 0)
+ {
+ // 3. 检测新报警并添加到旧报警列表,上传第一次报警信息
+ await DetectNewAlarmsAsync(newAlarms);
+
+ // 4. 显示新报警信息
+ DisplayAlarmInfo(newAlarms);
+ }
+
+ // 5. 检测已取消的报警并处理
+ var canceledAlarms = DetectCanceledAlarms(newAlarms);
+ if (canceledAlarms.Count > 0)
+ {
+ // 6. 记录已取消的报警信息(保存本地)
+ LogCanceledAlarms(canceledAlarms);
+
+ // 7. 上传已取消的报警信息(报警结束上传一次)
+ await UploadCanceledAlarmsAsync(canceledAlarms, strGuidAlarm);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteEX(ex);
+ }
+ }
+
+ ///
+ /// 从 PLC 读取报警状态
+ ///
+ private List ReadAlarmStatusFromPLC()
+ {
+ //string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ //JYResult resByte = CommonMethods.plcDevices[0].ReadValue>("D10150", DataType.ArrByte, 5000);
+
+ //if (resByte.IsSuccess && resByte != null)
+ //{
+ // int startPlcAddr = 10150; // 读取起始地址
+ // return PLCAlarmParseHelper.OmronFinsByte2Status(startPlcAddr, resByte.Content);
+ //}
+ string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ // 读取足够的字节覆盖所有可能的报警地址
+ int startAddr = 10010;
+ int endAddr = 10519; // 根据实际最大地址调整
+ int wordCount = endAddr - startAddr + 1;
+ int byteCount = wordCount * 2; // 每个字2字节
+
+ JYResult resByte = CommonMethods.plcDevices[0].ReadValue>(
+ $"D{startAddr}", DataType.ArrByte, (ushort)byteCount);
+
+ if (resByte.IsSuccess && resByte.Content != null)
+ {
+ return PLCAlarmParseHelper.OmronFinsByte2Status(startAddr, resByte.Content);
+ }
+
+ return null;
+ }
+
+ ///
+ /// 解析报警状态并生成新的报警列表
+ ///
+ private List ParseAlarmStatus(List alarmStatusList, string strGuidAlarm)
+ {
+ string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+ switch (LanguageManager.LanguageCode)
+ {
+ case "en"://英语报警信息
+ return alarmStatusList
+ .Where(s => s.Status)
+ .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData()
+ {
+ AlarmGuid = strGuidAlarm,
+ PLCAdress = s.PLCAdress,
+ AlarmCode = f.AlarmCode,
+ AlarmContent = f.EnAlarmContent == "" ? s.PLCAdress : f.EnAlarmContent,
+ AlarmType = "停机报警",
+ AlarmDesc = f.EnAlarmContent == "" ? s.PLCAdress : f.EnAlarmContent,
+ AlarmState = "1",
+ StartTime = Date,
+ EndTime = "",//Date
+ Flag = 0
+ }).ToList();
+ case "hu"://匈牙利语报警信息
+ return alarmStatusList
+ .Where(s => s.Status)
+ .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData()
+ {
+ AlarmGuid = strGuidAlarm,
+ PLCAdress = s.PLCAdress,
+ AlarmCode = f.AlarmCode,
+ AlarmContent = f.HuAlarmContent == "" ? s.PLCAdress : f.HuAlarmContent,
+ AlarmType = "停机报警",
+ AlarmDesc = f.HuAlarmContent == "" ? s.PLCAdress : f.HuAlarmContent,
+ AlarmState = "1",
+ StartTime = Date,
+ EndTime = "",//Date
+ Flag = 0
+ }).ToList();
+ case "ms"://马来语报警信息
+ return alarmStatusList
+ .Where(s => s.Status)
+ .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData()
+ {
+ AlarmGuid = strGuidAlarm,
+ PLCAdress = s.PLCAdress,
+ AlarmCode = f.AlarmCode,
+ AlarmContent = f.MsAlarmContent == "" ? s.PLCAdress : f.MsAlarmContent,
+ AlarmType = "停机报警",
+ AlarmDesc = f.MsAlarmContent == "" ? s.PLCAdress : f.MsAlarmContent,
+ AlarmState = "1",
+ StartTime = Date,
+ EndTime = "",//Date
+ Flag = 0
+ }).ToList();
+ case "zh"://中语报警信息
+ default:
+ return alarmStatusList
+ .Where(s => s.Status)
+ .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData()
+ {
+ AlarmGuid = strGuidAlarm,
+ PLCAdress = s.PLCAdress,
+ AlarmCode = f.AlarmCode,
+ AlarmContent = f.AlarmContent == "" ? s.PLCAdress : f.AlarmContent,
+ AlarmType = "停机报警",
+ AlarmDesc = f.AlarmContent == "" ? s.PLCAdress : f.AlarmContent,
+ AlarmState = "1",
+ StartTime = Date,
+ EndTime = "",//Date
+ Flag = 0
+ }).ToList();
+ }
+ }
+
+ ///
+ /// 检测新报警并添加到旧报警列表
+ ///
+ private async Task DetectNewAlarmsAsync(List newAlarms)
+ {
+ foreach (var alarm in newAlarms)
+ {
+ if (!listOld.Any(o => o.PLCAdress == alarm.PLCAdress))
+ {
+ listOld.Add(alarm);
+
+ // 4.5 新增逻辑(报警开始上传一次)
+ await UploadCanceledAlarmsAsync(newAlarms, strGuidAlarm);
+ }
+ }
+ }
+
+ ///
+ /// 显示报警信息
+ ///
+ private void DisplayAlarmInfo(List newAlarms)
+ {
+ StringBuilder sb = new StringBuilder();
+ newAlarms.ForEach(r => sb.Append($"{r.AlarmContent} "));
+
+ if (sb.Length > 0)
+ {
+ CommonMethods.SlideAlarmText = sb.ToString().Substring(0, sb.Length - 10);
+ }
+ }
+
+ ///
+ /// 检测已取消的报警
+ ///
+ private List DetectCanceledAlarms(List newAlarms)
+ {
+ var canceledAlarms = new List();
+ var listOldAlarm = new List(listOld);
+
+ foreach (var item in listOldAlarm)
+ {
+ if (!newAlarms.Any(n => n.PLCAdress == item.PLCAdress))
+ {
+ item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ canceledAlarms.Add(item);
+ listOld.Remove(item);
+ }
+ }
+
+ return canceledAlarms;
+ }
+
+ ///
+ /// 记录已取消的报警信息
+ ///
+ private void LogCanceledAlarms(List canceledAlarms)
+ {
+ var nowDate = DateTime.Now;
+ string basePath = $@"{CommonMethods.strAlarmLogspath}\{nowDate.Year}\{nowDate.Month}";
+ string fileName = $@"{nowDate.Day}.csv";
+ string title = "GUID,报警地址,故障代码,报警内容,开始时间,结束时间,是否停机";
+ string alarmContent = string.Join("\n", canceledAlarms.Select(m1 =>
+ $"{m1.AlarmGuid},{m1.PLCAdress},{m1.AlarmCode},{m1.AlarmContent},{m1.StartTime},{m1.EndTime},{m1.AlarmType}"));
+
+ CSVHelper.WriterCSV(basePath, fileName, title, alarmContent);
+
+ foreach (var m1 in canceledAlarms)
+ {
+ var alarm = new SysLog
+ {
+ InsertTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ LogType = "报警日志",
+ Note = m1.AlarmContent == "" ? m1.PLCAdress : m1.AlarmContent,
+ AlarmType = "错误",
+ Operater = CommonMethods.currentAdmin.LoginName,
+ VarName = m1.PLCAdress,
+ AlarmSet = "",
+ AlarmValue = "1",
+ };
+ // 显示报警信息
+ CommonMethods.AddAlarmLog(alarm);
+ // 告警TOP10保存
+ SaveAlarmCount(canceledAlarms);
+ }
+ }
+
+ ///
+ /// 上传已取消的报警信息
+ ///
+ private async Task UploadCanceledAlarmsAsync(List canceledAlarms, string strGuidAlarm)
+ {
+ if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected)
+ {
+ await CommonMethods.hbgMes.UploadEquAlarmAsync(
+ CommonMethods.mesConfig.DeviceAlarm,
+ CommonMethods.mesConfig.siteCode,
+ CommonMethods.mesConfig.lineCode,
+ CommonMethods.mesConfig.equipNum,
+ CommonMethods.mesConfig.MaterialCode,
+ CommonMethods.mesConfig.mesUserName,
+ canceledAlarms,
+ strGuidAlarm
+ );
+ }
+ }
+
+
+ ///
+ /// 告警TOP10保存
+ ///
+ private void SaveAlarmCount(List alarmDatas)
+ {
+ bool ISAdd = false;
+ var recordToUpdate = new AlarmCountEntry();
+
+ DateTime today = DateTime.Today;
+ DateTime upTime = DateTime.Now;
+ var listAlarmStatistics = CommonMethods.db.QueryWhereList(it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today);
+ List fd = new List();
+ if (listAlarmStatistics != null)
+ {
+ alarmDatas.ForEach(r =>
+ {
+ recordToUpdate = listAlarmStatistics.FirstOrDefault(x => x.PLCAdress.Trim() == r.PLCAdress);
+ if (recordToUpdate != null)
+ {
+ CommonMethods.db.Update(it => it.AlarmCount == it.AlarmCount + 1, it => it.PLCAdress == r.PLCAdress && Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today);
+ }
+ else
+ {
+ ISAdd = true;
+ AlarmCountEntry alarm = new AlarmCountEntry();
+ alarm.PLCAdress = r.PLCAdress;
+ alarm.AlarmContent = r.AlarmContent;
+ alarm.AlarmCount = 1;
+ alarm.Class = CommonMethods.strClass;
+ alarm.AlarmTime = upTime;
+ fd.Add(alarm);
+ }
+ });
+ if (ISAdd)
+ {
+ bool res = CommonMethods.db.AddReturnBool(fd);
+ ISAdd = false;
+ }
+ }
+ else
+ {
+ foreach (var m in listNew)
+ {
+ AlarmCountEntry alarm = new AlarmCountEntry();
+ alarm.PLCAdress = m.PLCAdress;
+ alarm.AlarmContent = m.AlarmContent;
+ alarm.AlarmCount = 1;
+ alarm.Class = CommonMethods.strClass;
+ alarm.AlarmTime = today;
+ fd.Add(alarm);
+ }
+ CommonMethods.db.AddReturnBool(fd);
+ }
+ GetASqlAlarmData(DateTime.Today, CommonMethods.strClass);
+ }
+
+
+ ///
+ /// 读取数据库报警信息
+ ///
+ ///
+ ///
+ public void GetASqlAlarmData(DateTime today, string strClass)
+ {
+ try
+ {
+ List chartAlarms = new List();
+ List AlarmDatas = null;
+ List List_X = new List();
+ List List_Y = new List();
+
+ DateTime baiClassStart = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 8:30:00"));
+ DateTime baiClassEnd = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 20:29:59"));
+
+ DateTime yeClassStart = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 20:30:00"));
+ DateTime yeClassEnd = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd 8:29:59"));
+
+
+ if (strClass == "全天".Translated())
+ {
+ AlarmDatas = CommonMethods.db.QueryList("AlarmCount", 10, it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today);
+ }
+ else if (strClass == "白班".Translated())
+ {
+ AlarmDatas = CommonMethods.db.QueryList("AlarmCount", 10, it => it.AlarmTime >= baiClassStart && it.AlarmTime <= baiClassEnd && it.Class == strClass);
+ }
+ else
+ {
+ AlarmDatas = CommonMethods.db.QueryList("AlarmCount", 10, it => it.AlarmTime >= yeClassStart && it.AlarmTime <= yeClassEnd && it.Class == strClass);
+ }
+
+ if (AlarmDatas != null && AlarmDatas.Count > 0)
+ {
+
+ for (int i = 0; i < AlarmDatas.Count; i++)
+ {
+ chartAlarms.Add(new ChartDataType()
+ {
+ DataType = AlarmDatas[i].AlarmContent,
+ DataCount = (int)AlarmDatas[i].AlarmCount
+ });
+ }
+ CommonMethods.Lst_AlarmTop10_Qty = chartAlarms;
+ }
+ else
+ {
+ CommonMethods.Lst_AlarmTop10_Qty = null;
+ }
+ }
+ catch (Exception)
+ {
+
+ }
+ }
+
+ #endregion
+
+ #region 下发权限给PLC
+ ///
+ /// 下发权限到PLC
+ ///
+ ///
+ private async Task SendRightToPLCAsync()
+ {
+ ushort quanxian = 1;
+ switch (CommonMethods.currentAdmin.LoginLevel)
+ {
+ case "操作员":
+ quanxian = 1;
+ break;
+ case "工程师":
+ quanxian = 2;
+ break;
+ case "管理员":
+ quanxian = 3;
+ break;
+ default:
+ break;
+ }
+
+
+ foreach (var plc in CommonMethods.plcDevices)
+ {
+ if (plcDeviceMapping.TryGetValue(plc, out var plcDevices))
+ {
+ foreach (var plcDevice in plcDevices)
+ {
+ if (plcDevice.IsConnected)
+ {
+ JYResult result = await Task.Run(() => plc.WriteValue("D21", (short)quanxian, DataType.Short));
+ if (result.IsSuccess)
+ {
+ CommonMethods.AddDataMonitorLog(1, $"Write_PLC_permission_Successful");
+ }
+ else
+ {
+ CommonMethods.AddDataMonitorLog(2, $"Write_PLC_permission(D15)_Failed");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region 获取白夜班次
+ bool qiehuan = false;
+ private void SetClass()
+ {
+ try
+ {
+ var nowDate = DateTime.Now;
+ string strClass = "";
+ DateTime time1 = Convert.ToDateTime($"{nowDate.ToShortDateString()} {CommonMethods.ChangeClassTime}");
+ DateTime time2 = Convert.ToDateTime($"{nowDate.ToShortDateString()} {Convert.ToDateTime(CommonMethods.ChangeClassTime).AddHours(12).ToString("HH:mm:ss")}");
+ if (nowDate <= time1 && nowDate >= time2)
+ {
+ if (CommonMethods.plcDevices[0].IsConnected)
+ {
+ if (!qiehuan)
+ {
+ if (!SetPlcTime())
+ {
+ CommonMethods.AddLog(true, $"PC写入PLC时间失败".Translated());
+ }
+ else
+ {
+ qiehuan = true;
+ CommonMethods.AddLog(false, "PC写入PLC时间成功".Translated());
+ }
+ }
+ }
+ strClass = "白班".Translated();
+ }
+ else
+ {
+ qiehuan = false;
+ strClass = "夜班".Translated();
+ }
+ CommonMethods.strClass = strClass;
+ }
+ catch (Exception ex)
+ {
+ CommonMethods.AddLog(true, "PC写入PLC时间异常".Translated() + ":" + ex.Message);
+ LogHelper.Instance.WriteEX(ex);
+ }
+ }
+ #endregion
+
+ #region 写入PLC时间
+ ///
+ /// 写当前时间给PLC
+ ///
+ private bool SetPlcTime()
+ {
+ var nowDate = DateTime.Now;
+ string yearStr = nowDate.Year.ToString();
+
+
+ bool IsSuccess = false;
+
+ // 为防止PLC时间出现偏差,每次自动运行的时候写PLC系统时间
+ short[] writeArr = new short[]
+ {
+ Convert.ToInt16(nowDate.Year),//D70
+ Convert.ToInt16(nowDate.Month),//D71
+ Convert.ToInt16(nowDate.Day),//D72
+ Convert.ToInt16(nowDate.Hour),//D73
+ Convert.ToInt16(nowDate.Minute),//D74
+ Convert.ToInt16(nowDate.Second),//D75
+ (short)1 //D76
+ };
+ if (CommonMethods.plcDevices[0].IsConnected)
+ {
+ JYResult result = CommonMethods.plcDevices[0].WriteValue("D70", writeArr, DataType.ArrShort);
+ if (result.IsSuccess)
+ {
+ IsSuccess = true;
+ }
+ }
+ return IsSuccess;
+
+ }
+
+ #region 9、IDisposable 实现
+
+
+ // 如果需要在StopAsync中也显示进度,可以添加一个进度回调
+
+ private Task processTask;
+
+ public async Task StopAsync(IProgress progress = null)
+ {
+ _cts.Cancel();
+
+ if (_heartbeatTask != null)
+ {
+ progress?.Report("正在停止心跳任务...");
+ try
+ {
+ await _heartbeatTask;
+ }
+ catch (OperationCanceledException)
+ {
+ // 忽略取消异常
+ }
+ }
+
+ if (processTask != null)
+ {
+ progress?.Report("正在停止处理任务...");
+ try
+ {
+ await processTask;
+ }
+ catch (OperationCanceledException)
+ {
+ // 忽略取消异常
+ }
+ }
+
+ foreach (var pair in groupTasks)
+ {
+ progress?.Report($"正在停止组任务: {pair.Key}...");
+ try
+ {
+ await pair.Value;
+ }
+ catch (OperationCanceledException)
+ {
+ // 忽略取消异常
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _cts?.Cancel();
+ _cts?.Dispose();
+
+ processTask?.Wait();
+
+ // 断开所有设备连接
+ foreach (var plc in CommonMethods.plcDevices)
+ {
+ plc.Disconnect();
+ //if (plcAlarmReaders.TryGetValue(plc, out var alarmReader))
+ //{
+ // alarmReader.PLCAlarm_New -= AlrmReader_PLCAlarm_New;
+ // alarmReader.PLCAlarm_Remove -= AlrmReader_PLCAlarm_Remove;
+ //}
+ }
+
+ CommonMethods.plcDevices.Clear();
+ plcDeviceMapping.Clear();
+ plcAlarmReaders.Clear();
+ CommonMethods.plcStatuses.Clear();
+
+ DisconnectAllPlcs();
+ }
+ }
+
+
+
+ #endregion
+ #endregion
+
+ #region 7、辅助方法
+
+ private static PLCType GetPLCTypeEnum(string plcTypeString)
+ {
+ if (Enum.TryParse(plcTypeString, out PLCType plcType))
+ {
+ return plcType;
+ }
+ throw new ArgumentException($"PLC 类型无效: {plcTypeString}");
+ }
+
+
+
+ #endregion
+ }
+}
diff --git a/JinYuan.ControlCenter/ControlCenter_Ext.cs b/JinYuan.ControlCenter/ControlCenter_Ext.cs
new file mode 100644
index 0000000..bd636f1
--- /dev/null
+++ b/JinYuan.ControlCenter/ControlCenter_Ext.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using JinYuan.Helper;
+using JinYuan.Models;
+using JinYuan.VirtualDataLibrary;
+using PLCCommunication.Common;
+using PLCCommunication.Common.DataConvert;
+
+namespace JinYuan.ControlCenters
+{
+ public partial class ControlCenter
+ {
+ public static string GetValueFromByteArray(byte[] bArray, string PLCGroupVariablekey, int offset = 0, Group pf = null)
+ {
+ return GetValueFromByteArray(bArray, getPLCVariable(PLCGroupVariablekey), offset, pf);
+ }
+
+ public static PLCGroupVariable getPLCVariable(string key)
+ {
+ PLCGroupVariable pLCGroupVariable = new PLCGroupVariable();
+ CommonMethods.PLCVariable.TryGetValue(key, out pLCGroupVariable);
+ return pLCGroupVariable;
+ }
+
+ ///
+ /// 获取MES返回结果
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string GetValueFromByteArray(byte[] bArray, PLCGroupVariable var, int offset = 0, Group pf = null)
+ {
+ string value = "0";
+ try
+ {
+ switch (var.DataType)
+ {
+ case "Float":
+ value = FloatLib.GetFloatFromByteArray(bArray, var.ArrayStart * 2 + offset, DataFormat.CDAB).ToString("0.###");
+ break;
+ case "Short":
+ value = ShortLib.GetShortFromByteArray(bArray, var.ArrayStart * 2 + offset).ToString();
+ break;
+ case "Int":
+ value = IntLib.GetIntFromByteArray(bArray, var.ArrayStart * 2 + offset).ToString();
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"数据解析异常, 原因: {ex}", "SysErrorLog");
+ }
+ return value;
+ }
+ }
+}
diff --git a/JinYuan.ControlCenter/ControlCenter_Plc.cs b/JinYuan.ControlCenter/ControlCenter_Plc.cs
new file mode 100644
index 0000000..83ef067
--- /dev/null
+++ b/JinYuan.ControlCenter/ControlCenter_Plc.cs
@@ -0,0 +1,2078 @@
+
+using JinYuan.Helper;
+using JinYuan.MES.Enums;
+using JinYuan.MES.Models;
+using JinYuan.Models;
+using JinYuan.VirtualDataLibrary;
+using JinYuan.VirtualDataLibrary.Utlis;
+using Language;
+using Microsoft.SqlServer.Server;
+using PLC;
+using PLCCommunication;
+using PLCCommunication.BasicFramework;
+using PLCCommunication.Common;
+using PLCCommunication.Common.DataConvert;
+using SqlSugar;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Eventing.Reader;
+using System.IO;
+using System.Net;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Xml.Linq;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement;
+
+namespace JinYuan.ControlCenters
+{
+ public partial class ControlCenter
+ {
+ public object FileLock = new object();//文件锁
+
+
+ #region 保留2位小数
+ private float SaveTwoNum(float TNum)
+ {
+ //1.2333 0.998
+ int value = (int)(TNum * 1000) % 10;
+ value = value > 4 ? 10 - value : -value;
+ TNum += ((float)value / 1000);
+ //保留3位小数
+ TNum = (float)(int)(TNum * 1000) / 1000;
+ return TNum;
+ }
+ #endregion
+
+
+ #region 包装上料工位(数据处理)
+ ///
+ /// 包装箱上料工位
+ ///
+ private async Task FeedingStation(JYResult bArrays, object plcModel)
+ {
+ PlcGroup pf = plcModel as PlcGroup;
+ if (pf == null)
+ {
+ LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ return;
+ }
+
+ using (var timer = new TimedOperation("包装箱进站", (elapsedMs, operationName) =>
+ {
+ CommonMethods.AddDataMonitorLog(0, $"包装箱进站共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ }))
+ {
+ if (bArrays.IsSuccess && bArrays.Content != null)
+ {
+ try
+ {
+ #region 数据初始化
+ //解析数据
+ List cartonTagInfos = new List();
+ const int CODE_SIZE = 40;
+ #endregion
+
+ #region 数据解析
+ byte[] bytes = bArrays.Content.ByteReverse();//高低位转换
+ string containerCode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, CODE_SIZE -1, System.Text.Encoding.ASCII).// 从D3100开始
+ Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ #endregion
+
+ #region 获取箱唛信息MES
+ if (String.IsNullOrEmpty(containerCode) || containerCode.Trim() == "ERROR" || !IsAlphaNumeric(containerCode))
+ {
+ CommonMethods.AddDataMonitorLog(0, "PLC中读取箱唛码NG:" + containerCode);
+ var entity = new AGearEntity
+ {
+ ContainerCode = "NG",
+ PalletCode = "NG",
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "包装箱进站",
+ Result = "NG"
+ };
+
+ var entityList = new List { entity };
+ // 线程安全调用委托
+ CommonMethods.ShowFeedingDelegate.Invoke(entityList);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[1].VarAddress, 1));
+ return;
+ }
+
+ if (CommonMethods.sysConfig.MesModeSwitching)
+ {
+ try
+ {
+ await GetCartonTagInfosAsync(pf, containerCode);
+ }
+ catch (Exception ex)
+ {
+ CommonMethods.AddDataMonitorLog(0, "保存箱唛信息失败,箱唛码:" + containerCode + ",异常信息:" + ex.Message);
+ }
+
+
+
+ #region 箱唛数据反馈&保存
+ if (cartonTagInfos.Count > 0)
+ {
+ try
+ {
+ await SaveCartonTagInfosAsync(containerCode, cartonTagInfos);
+ }
+ catch (Exception ex)
+ {
+ CommonMethods.AddDataMonitorLog(0, "保存箱唛信息失败,箱唛码:" + containerCode + ",异常信息:" + ex.Message);
+ }
+ }
+ else
+ {
+ CommonMethods.AddDataMonitorLog(0, "未从mes中获取到箱唛信息,箱唛码:" + containerCode);
+ }
+ #endregion
+
+ #region 获取栈板信息
+ (var palletCode, var palletTagInfos) = await GetPalletTagInfosAsync(pf, containerCode);
+ #endregion
+
+ #region 栈板数据反馈&保存
+ if (palletTagInfos.Count > 0)
+ {
+ try
+ {
+ await SavePalletTagInfosAsync(containerCode, palletCode, palletTagInfos);
+ }
+ catch (Exception ex)
+ {
+ CommonMethods.AddDataMonitorLog(0, "保存栈板信息失败,栈板码:" + palletCode + ",异常信息:" + ex.Message);
+ }
+ }
+ else
+ {
+ CommonMethods.AddDataMonitorLog(0, "未从mes中获取到栈板信息,栈板码:" + palletCode);
+ }
+ await SaveFeedingStationAsync(containerCode, palletCode);
+
+ #endregion
+ //if (palletTagInfos.Count > 0 && cartonTagInfos.Count > 0)
+ // await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[2].VarAddress, 1));
+ //else
+ // await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[2].VarAddress, 2));
+
+
+
+ var taginfo = cartonTagInfos.FirstOrDefault(x => x.tagCode == "model");
+ int model = CommonMethods.mesConfig.GetModel(taginfo.tagValue);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[3].VarAddress, (short)model));
+ }
+ else if (CommonMethods.sysConfig.IsDebugMode)
+ {
+ string batModel = CommonMethods.mesConfig.ModelName;
+ TagInfo kVDetail = new TagInfo()
+ {
+ tagCode="model",
+ tagValue = batModel
+ };
+ cartonTagInfos.Add(kVDetail);
+ await SaveCartonTagInfosAsync(containerCode, cartonTagInfos);
+ string palletCode = containerCode + "_01";
+ await SaveFeedingStationAsync(containerCode, palletCode);
+
+
+ int model = CommonMethods.mesConfig.GetModel(batModel);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[3].VarAddress, (short)model));
+ }
+ else
+ {
+ CommonMethods.AddDataMonitorLog(0, "MES 功能未开启,无法获取箱唛和栈板信息");
+ //await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[2].VarAddress, 2));
+ }
+
+ #endregion
+
+
+ ////O2 客户还是普通客户
+ //short customertype = CommonMethods.mesConfig.customerType == "O2" ? (short)1 : (short)2;
+ //await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[2].VarAddress, customertype));
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"获取箱唛和栈板信息线程处理异常:{ex}", "SysErrorLog");
+ // 异常时反馈PLC错误
+ await WritePlcResult(pf, "D80", 1, PLC.DataType.Short);
+ }
+ finally
+ {
+ // 重置PLC信号
+ await Task.Run(() =>
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[1].VarAddress, 1)
+ );
+ }
+ }
+ }
+ }
+
+ ///
+ /// 保存包装箱进站数据
+ ///
+ ///
+
+ private async Task SaveFeedingStationAsync(string containerCode, string palletCode)
+ {
+ var entity = new AGearEntity
+ {
+ ContainerCode = containerCode,
+ PalletCode = palletCode,
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "包装箱进站",
+ Result = "成功"
+ };
+
+ if (string.IsNullOrWhiteSpace(containerCode))
+ {
+ LogHelper.Instance.WriteError("包装箱码为空,无法保存进站信息", "SysErrorLog");
+ entity.ContainerCode = "NG";
+ entity.Remark = "NG";
+ }
+ if (string.IsNullOrWhiteSpace(palletCode))
+ {
+ LogHelper.Instance.WriteLog($"包装箱[{containerCode}]对应的栈板码码为空");
+ entity.PalletCode = "NG";
+ entity.Remark = "NG";
+ }
+
+ try
+ {
+ if (entity.ContainerCode != "NG")
+ {
+ bool con_success = await CommonMethods.db.AddReturnBoolAsync(entity);
+
+ string logMsg = con_success
+ ? $"包装箱[{containerCode}](栈板码码[{palletCode}])进站信息存储成功"
+ : $"包装箱[{containerCode}](栈板码码[{palletCode}])进站信息存储失败";
+ CommonMethods.AddDataMonitorLog(0, logMsg);
+ }
+
+
+ var entityList = new List { entity };
+ // 线程安全调用委托
+ CommonMethods.ShowFeedingDelegate.Invoke(entityList);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError(containerCode + $"包装箱进站信息保存处理异常:{ex}", "SysErrorLog");
+ }
+
+ }
+ #endregion
+
+
+ #region 获取箱唛信息
+ private async Task> GetCartonTagInfosAsync(PlcGroup pf, string containerCode)
+ {
+ List cartonTagInfos = new List();
+ const int maxRetry = 3; // 最大重试3次
+ const int retryIntervalMs = 1000; // 重试间隔1秒
+ int retryCount = 0;
+ //bool success = false;
+ while (retryCount < maxRetry)
+ {
+ if (!CommonMethods.mesConfig.isConnected)
+ {
+ CommonMethods.AddDataMonitorLog(1, "MES连接中断,停止上传"); // 1:错误级别
+ await WritePlcResult(pf, "D80", 1, PLC.DataType.Short); // 反馈错误
+ break;
+ }
+
+ var (success, mesMessage, mesResType, cartonTags) = await CommonMethods.hbgMes.QueryCartonAsync(
+ CommonMethods.mesConfig.queryCartonUrl,
+ CommonMethods.mesConfig.siteCode,
+ CommonMethods.mesConfig.lineCode,
+ CommonMethods.mesConfig.equipNum,
+ containerCode
+ );
+
+ if (success && cartonTags.Count > 0)
+ {
+ cartonTagInfos = cartonTags;
+ await WritePlcResult(pf, "D80", 0, PLC.DataType.Short); // 反馈成功
+ break;
+ }
+
+ // 失败处理
+ retryCount++;
+ CommonMethods.AddDataMonitorLog(1, $"MES请求失败({retryCount}/{maxRetry}):{mesMessage}");
+ if (retryCount >= maxRetry)
+ {
+ await WritePlcResult(pf, "D80", 1, PLC.DataType.Short); // 超过重试次数,反馈错误
+ break;
+ }
+ else
+ {
+ await Task.Delay(retryIntervalMs); // 等待重试
+ }
+ }
+
+ return cartonTagInfos;
+
+ }
+
+ #endregion
+
+
+ #region 保存箱唛信息数据
+ ///
+ /// 保存箱唛信息数据
+ ///
+ ///
+
+ private async Task SaveCartonTagInfosAsync(string containerCode, List list)
+ {
+ if (list == null || list.Count == 0)
+ return;
+
+ // 明确指定为异步 lambda
+ try
+ {
+ ContainerEntity containerEntity = new ContainerEntity
+ {
+ container_id = containerCode,
+ create_time = DateTime.Now
+ };
+
+
+ List containerKVList = list
+ .Select(tag => new ContainerKVDetail
+ {
+ container_id = containerCode,
+ tag_code = tag.tagCode,
+ tag_value = tag.tagValue,
+ create_time = DateTime.Now
+ })
+ .ToList();
+
+ // 数据库操作
+ bool con_success = await CommonMethods.db.AddReturnBoolAsync(containerEntity);
+ bool kv_success = await CommonMethods.db.AddReturnBoolAsync(containerKVList);
+ if (kv_success)
+ {
+ CommonMethods.AddDataMonitorLog(0, containerCode + "箱唛信息存储成功");
+ }
+ else
+ {
+ CommonMethods.AddDataMonitorLog(0, containerCode + "箱唛信息存储失败");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError(containerCode + $"箱唛信息保存处理异常:{ex}", "SysErrorLog");
+ }
+
+ }
+
+ #endregion
+
+
+ #region 获取栈板信息
+ private async Task<(string palletCode, List palletTagInfos)> GetPalletTagInfosAsync(PlcGroup pf, string containerCode)
+ {
+ string palletCode = string.Empty;
+ List palletTagInfos = new List();
+ const int maxRetry = 3; // 最大重试3次
+ const int retryIntervalMs = 1000; // 重试间隔1秒
+ int retryCount = 0;
+
+ if (!CommonMethods.mesConfig.isConnected)
+ {
+ CommonMethods.AddDataMonitorLog(1, "MES连接中断");
+ await WritePlcResult(pf, "D80", 1, PLC.DataType.Short); // 反馈错误
+ return (palletCode, palletTagInfos);
+ }
+
+ // 调用MES接口
+ var (success, mesMessage, mesResType, retPalletCode, retPalletTags) = await CommonMethods.hbgMes.QueryPalletAsync(
+ CommonMethods.mesConfig.queryPalletUrl,
+ CommonMethods.mesConfig.siteCode,
+ CommonMethods.mesConfig.lineCode,
+ CommonMethods.mesConfig.equipNum,
+ containerCode
+ );
+
+ if (!success || retPalletTags == null || retPalletTags.Count == 0)
+ {
+ CommonMethods.AddDataMonitorLog(1, "MES获取栈板信息失败");
+ await WritePlcResult(pf, "D80", 1, PLC.DataType.Short); // 反馈错误
+ return (palletCode, palletTagInfos);
+ }
+
+ // 赋值有效数据
+ palletCode = retPalletCode;
+ palletTagInfos = retPalletTags;
+
+ return (palletCode, palletTagInfos);
+ }
+ #endregion
+
+
+ #region 保存栈板信息数据
+ ///
+ /// 保存栈板信息数据
+ ///
+ ///
+
+ private async Task SavePalletTagInfosAsync(string containerCode, string pallet_id, List list)
+ {
+ if (list == null || list.Count == 0)
+ return;
+
+ // 明确指定为异步 lambda
+ try
+ {
+ PalletEntity palletEntity = new PalletEntity
+ {
+ container_id = containerCode,
+ pallet_id = pallet_id,
+ create_time = DateTime.Now
+ };
+
+ List palletKVList = list
+ .Select(tag => new PalletKVDetail
+ {
+ pallet_id = pallet_id,
+ tag_code = tag.tagCode,
+ tag_value = tag.tagValue,
+ create_time = DateTime.Now
+ })
+ .ToList();
+
+ // 数据库操作
+ bool con_success = await CommonMethods.db.AddReturnBoolAsync(palletEntity);
+ bool kv_success = await CommonMethods.db.AddReturnBoolAsync(palletKVList);
+
+ if (con_success && kv_success)
+ {
+ CommonMethods.AddDataMonitorLog(0, containerCode + "栈板信息存储成功");
+ }
+ else
+ {
+ CommonMethods.AddDataMonitorLog(0, containerCode + "栈板信息存储失败");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError(containerCode+ "_" + $"栈板信息保存处理异常:{ex}", "SysErrorLog");
+ }
+
+ }
+
+ #endregion
+
+
+ private async Task FeedingStationB(JYResult bArrays, object plcModel)
+ {
+ PlcGroup pf = plcModel as PlcGroup;
+ if (pf == null)
+ {
+ LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ return;
+ }
+
+ using (var timer = new TimedOperation("中部扫描", (elapsedMs, operationName) =>
+ {
+ CommonMethods.AddDataMonitorLog(0, $"中部扫描共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ }))
+ {
+ if (bArrays.IsSuccess && bArrays.Content != null)
+ {
+ try
+ {
+ #region 数据初始化
+ //解析数据
+ List cartonTagInfos = new List();
+ const int CODE_SIZE = 40;
+ #endregion
+
+ #region 数据解析
+ byte[] bytes = bArrays.Content.ByteReverse();//高低位转换
+ string containerCode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, CODE_SIZE - 1, System.Text.Encoding.ASCII).// 从D3100开始
+ Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ #endregion
+
+ //#region 获取箱唛信息
+ if (String.IsNullOrEmpty(containerCode) || containerCode.Trim() == "ERROR" || IsAlphaNumeric(containerCode))
+ {
+ CommonMethods.AddDataMonitorLog(0, "PLC中读取箱唛码NG:" + containerCode);
+ var entity = new AGearEntity
+ {
+ ContainerCode = "NG",
+ PalletCode = "NG",
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "中部扫描包装箱",
+ Result = "NG"
+ };
+
+ var entityList = new List { entity };
+ // 线程安全调用委托
+ //CommonMethods.ShowFeedingDelegate.Invoke(entityList);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[1].VarAddress, 1));
+ return;
+ }
+
+ ContainerKVDetail modelDetail = CommonMethods.db.Query(it => it.container_id == containerCode && it.tag_code == "model");
+ //string palletCode = cartonEntity.ContainerCode;
+ //if (cartonEntity == null || string.IsNullOrWhiteSpace(palletCode))
+ //{
+ // LogHelper.Instance.WriteLog($"包装箱[{containerCode}]对应的栈板码为空");
+ // await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[4].VarAddress, 1));
+ // return;
+ //}
+ string batModel = modelDetail.tag_code;
+ int model = CommonMethods.mesConfig.GetModel(batModel);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[3].VarAddress, (short)model));
+
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"中部扫描获取箱唛信息线程处理异常:{ex}", "SysErrorLog");
+
+ }
+ finally
+ {
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[1].VarAddress, 1));
+ }
+ }
+ }
+ }
+
+
+ private async Task FeedingStationC(JYResult bArrays, object plcModel)
+ {
+ PlcGroup pf = plcModel as PlcGroup;
+ if (pf == null)
+ {
+ LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ return;
+ }
+
+ using (var timer = new TimedOperation("尾部扫描", (elapsedMs, operationName) =>
+ {
+ CommonMethods.AddDataMonitorLog(0, $"尾部扫描共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ }))
+ {
+ if (bArrays.IsSuccess && bArrays.Content != null)
+ {
+ try
+ {
+ #region 数据初始化
+ //解析数据
+ List cartonTagInfos = new List();
+ const int CODE_SIZE = 40;
+ #endregion
+
+ #region 数据解析
+ byte[] bytes = bArrays.Content.ByteReverse();//高低位转换
+ string containerCode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, CODE_SIZE - 1, System.Text.Encoding.ASCII).// 从D3100开始
+ Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ #endregion
+
+ //#region 获取箱唛信息
+ if (String.IsNullOrEmpty(containerCode) || containerCode.Trim() == "ERROR" || IsAlphaNumeric(containerCode))
+ {
+ CommonMethods.AddDataMonitorLog(0, "PLC中读取箱唛码NG:" + containerCode);
+ var entity = new AGearEntity
+ {
+ ContainerCode = "NG",
+ PalletCode = "NG",
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "尾部扫描包装箱",
+ Result = "NG"
+ };
+
+ var entityList = new List { entity };
+ // 线程安全调用委托
+ //CommonMethods.ShowFeedingDelegate.Invoke(entityList);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[1].VarAddress, 1));
+ return;
+ }
+ ContainerKVDetail modelDetail = CommonMethods.db.Query(it => it.container_id == containerCode && it.tag_code == "model");
+ string batModel = modelDetail.tag_code;
+
+ int model = CommonMethods.mesConfig.GetModel(batModel);
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[3].VarAddress, (short)model));
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"尾部扫描获取箱唛信息线程处理异常:{ex}", "SysErrorLog");
+
+ }
+ finally
+ {
+ await Task.Run(() => CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[1].VarAddress, 1));
+ }
+ }
+ }
+ }
+
+
+ #region 箱唛打印
+ ///
+ /// 箱唛打印
+ ///
+ ///
+ ///
+ ///
+ private async Task PrintContainerStation(JYResult bArrays, object plcModel)
+ {
+ PlcGroup pf = plcModel as PlcGroup;
+ if (pf == null)
+ {
+ LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ return;
+ }
+
+ if (CommonMethods.mesConfig.customerType == "O2")
+ {
+ // 重置PLC信号
+ await Task.Run(() =>
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ );
+ CommonMethods.AddDataMonitorLog(0, "上位机不打印O2客户箱唛标签.");
+ return;
+ }
+
+
+
+
+ using (var timer = new TimedOperation("箱唛标签打印", (elapsedMs, operationName) =>
+ {
+ CommonMethods.AddDataMonitorLog(0, $"箱唛标签打印共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ }))
+ {
+ if (bArrays.IsSuccess && bArrays.Content != null)
+ {
+ List listRes = GetList(1, 2);//默认为2,判断箱唛码
+ byte[] bytes = bArrays.Content.ByteReverse();
+ string containercode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, 39, System.Text.Encoding.ASCII).// D4610~4629
+ Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ if (String.IsNullOrEmpty(containercode) || containercode.Contains("ERROR") || containercode.Contains("NG"))
+ {
+ var entity = new DGearEntity
+ {
+ ContainerCode = "NG",
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "箱唛标签打印",
+ Result = "NG"
+ };
+
+ var entityList = new List { entity };
+ CommonMethods.ShowPrintContainergDelegate?.Invoke(entityList);
+ //CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+
+ CommonMethods.AddDataMonitorLog(0, "获取箱唛码为空,请检查".Translated());
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//置位0
+ return;
+ }
+ else
+ {
+ listRes = await PrintContainer(containercode, listRes);
+ await SavePrintContainerAsync(containercode);
+ //CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//复位
+ }
+ }
+ }
+ }
+
+ private static readonly Dictionary CartonFieldMappings = new Dictionary
+ {
+ { "customer", "57" },
+ { "supplier", "EVE" },
+ { "orderNumber", "19999" },
+ { "materialCode", "1" },
+ { "productModel", "123" },
+ { "productDate", "2026-01-15" },
+ { "batchQuantity", "240 " },
+ { "packingQuantity", "10 " },
+ { "level", "A" },
+ { "voltage", "100" },
+ { "resistance", "1000" },
+ { "capacity", "99" },
+ { "boxNumber", "test111" },
+ { "groupNumber", "88" },
+ { "remark", "测试" }
+ };
+
+ private async Task> PrintContainer(string containercode, List listRes)
+ {
+ if (CommonMethods.containerPrint == null)
+ {
+ if (!string.IsNullOrEmpty(CommonMethods.btwConfig.printerName))
+ {
+ CommonMethods.containerPrint = new BartenderPrinter(CommonMethods.btwConfig.printerName);
+ }
+ else
+ {
+ listRes[0] = 3;
+ LogHelper.Instance.WriteLog($"打印机未设置和连接", "printLog");
+ return listRes;
+ }
+ }
+ try
+ {
+ List container_kv_data = null;
+ // 初始化目标字典
+ Dictionary vdaPrintValues = new Dictionary();
+
+ if (!CommonMethods.sysConfig.IsDebugMode)
+ {
+ container_kv_data = CommonMethods.db.QueryWhereList(it => it.container_id == containercode);
+ if (container_kv_data == null || !container_kv_data.Any())
+ {
+ listRes[0] = 3;
+ CommonMethods.AddDataMonitorLog(0, $"container:{containercode}获取参数失败".Translated());
+ LogHelper.Instance.WriteLog($"container:{containercode}获取参数失败", "printLog");
+ return listRes;
+ }
+
+ MapCartonKvDataToPrintDict(container_kv_data, vdaPrintValues);
+ }
+ else
+ {
+ FillDebugPrintCartonData(vdaPrintValues);
+ }
+
+ //使用zebra打印机
+ bool b = CommonMethods.containerPrint.write(CommonMethods.btwConfig.containerBTWPath, "carton label", vdaPrintValues);
+ if (b)
+ {
+ listRes[0] = 1;
+ CommonMethods.AddDataMonitorLog(0, $"箱唛码{containercode}:打印发送成功".Translated());
+ }
+ else
+ {
+ listRes[0] = 2;
+ CommonMethods.AddDataMonitorLog(0, $"箱唛码{containercode}:打印发送失败".Translated());
+ LogHelper.Instance.WriteLog($"{containercode}发送失败", "printLog");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"箱唛打印线程异常:{ex}", "printLog");
+ }
+ return listRes;
+ }
+
+ ///
+ /// 将KV数据映射到打印字典(业务转换层)
+ ///
+ /// KV数据列表
+ /// 打印字典
+ private void MapCartonKvDataToPrintDict(List kvData, Dictionary printDict)
+ {
+ var kvDict = kvData.ToDictionary(kv => kv.tag_code, kv => kv.tag_value, StringComparer.OrdinalIgnoreCase);
+ foreach (var mapping in CartonFieldMappings)
+ {
+ if (kvDict.TryGetValue(mapping.Value, out var value))
+ {
+ printDict[mapping.Key] = value ?? string.Empty; // 空值处理
+ }
+ else
+ {
+ LogHelper.Instance.WriteLog($"托盘数据缺失字段:{mapping.Key}(对应TagCode:{mapping.Value})", "printLog");
+ printDict[mapping.Key] = string.Empty;
+ }
+ }
+ }
+
+ ///
+ /// 填充调试模式的测试数据(测试层)
+ ///
+ /// 打印字典
+ private void FillDebugPrintCartonData(Dictionary printDict)
+ {
+ printDict["customer"] = "57";
+ printDict["supplier"] = "EVE";
+ printDict["orderNumber"] = "19999";
+ printDict["materialCode"] = "1";
+ //printDict["产品描述"] = "方形铁锂";
+ printDict["productModel"] = "123";
+ printDict["productDate"] = "2026-01-15";
+ printDict["batchQuantity"] = "240 ";
+ printDict["packingQuantity"] = "10 ";
+ printDict["level"] = "A";
+ printDict["voltage"] = "100";
+ printDict["resistance"] = "1000";
+ printDict["capacity"] = "99";
+ printDict["boxNumber"] = "test111";
+ printDict["groupNumber"] = "88";
+ printDict["remark"] = "测试";
+ }
+
+ ///
+ /// 保存包装箱进站数据
+ ///
+ ///
+ private async Task SavePrintContainerAsync(string containerCode)
+ {
+ var entity = new DGearEntity
+ {
+ ContainerCode = containerCode,
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "箱唛标签打印",
+ Result = "成功"
+ };
+
+ // 入参校验(避免空值写入数据库)
+ if (string.IsNullOrWhiteSpace(containerCode))
+ {
+ LogHelper.Instance.WriteError("包装箱码为空,无法保存进站信息", "SysErrorLog");
+ entity.ContainerCode = "NG";
+ entity.Result = "NG";
+ }
+
+ // 明确指定为异步 lambda
+ try
+ {
+
+ if (entity.ContainerCode != "NG")
+ {
+ // 数据库操作
+ bool con_success = await CommonMethods.db.AddReturnBoolAsync(entity);
+
+ string logMsg = con_success
+ ? $"包装箱[{containerCode}]箱唛打印信息存储成功"
+ : $"包装箱[{containerCode}]箱唛打印信息存储失败";
+ CommonMethods.AddDataMonitorLog(0, logMsg);
+ }
+
+ var entityList = new List { entity };
+ CommonMethods.ShowPrintContainergDelegate?.Invoke(entityList);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError(containerCode + $"包装箱进站信息保存处理异常:{ex}", "SysErrorLog");
+ }
+
+ }
+ #endregion
+
+ #region 栈板打印
+ ///
+ /// 栈板打印
+ ///
+ ///
+ ///
+ ///
+ private async Task PrintPalletStation(JYResult bArrays, object plcModel)
+ {
+ PlcGroup pf = plcModel as PlcGroup;
+ if (pf == null)
+ {
+ LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ return;
+ }
+
+ if (CommonMethods.mesConfig.customerType == "O2")
+ {
+ // 重置PLC信号
+ await Task.Run(() =>
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ );
+ CommonMethods.AddDataMonitorLog(0, "上位机不打印O2客户栈板标签.");
+ return;
+ }
+
+
+ using (var timer = new TimedOperation("栈板打印", (elapsedMs, operationName) =>
+ {
+ CommonMethods.AddDataMonitorLog(0, $"栈板打印共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ }))
+ {
+ if (!CommonMethods.sysConfig.IsDebugMode)
+ {
+ if (bArrays.IsSuccess && bArrays.Content != null)
+ {
+ List listRes = GetList(1, 2);//默认为2,判断栈板码
+ byte[] bytes = bArrays.Content.ByteReverse();
+ string palletcode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, 39, System.Text.Encoding.ASCII).// D4610~4629
+ Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ if (String.IsNullOrEmpty(palletcode) || palletcode.Contains("ERROR") || palletcode.Contains("NG"))
+ {
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//置位0
+ CommonMethods.AddDataMonitorLog(0, "获取栈板码为空,请检查".Translated());
+ return;
+ }
+ else
+ {
+ listRes = await PrintPallet(palletcode, listRes);
+ await SavePrintPalletAsync(palletcode);
+
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//复位
+ }
+
+ }
+ }
+ else
+ {
+ List listRes = GetList(1, 2);
+ string palletcode = "222222";
+ listRes = await PrintPallet(palletcode, listRes);
+ }
+
+ }
+ }
+
+ private static readonly Dictionary PalletFieldMappings = new Dictionary
+ {
+ { "customer", "customer" }, // 客户编码
+ { "groupNumber", "groupNumber" }, // 组号
+ { "orderNumber", "orderNumber" }, // 订单号
+ { "packageQty", "packageQty" }, // 包装数量
+ { "productCode", "productCode" }, // 产品编码
+ { "palletQty", "palletQty" }, // 托盘数量
+ { "palletNo", "palletNo" }, // 托盘编号
+ { "remark", "remark" } // 备注
+ };
+
+ private async Task> PrintPallet(string palletcode, List listRes)
+ {
+ if (CommonMethods.palletPrint == null)
+ {
+ if (!string.IsNullOrEmpty(CommonMethods.btwConfig.printerName))
+ {
+ CommonMethods.palletPrint = new BartenderPrinter(CommonMethods.btwConfig.printerName);
+ }
+ else
+ {
+ listRes[0] = 3;
+ LogHelper.Instance.WriteLog($"打印机未设置和连接", "printLog");
+ return listRes;
+ }
+ }
+
+ try
+ {
+ List pallet_kv_data = null;
+ // 初始化目标字典
+ Dictionary vdaPrintValues = new Dictionary();
+ if (!CommonMethods.sysConfig.IsDebugMode)
+ {
+ pallet_kv_data = CommonMethods.db.QueryWhereList(it => it.pallet_id == palletcode);
+ if (pallet_kv_data == null || !pallet_kv_data.Any())
+ {
+ listRes[0] = 3;
+ CommonMethods.AddDataMonitorLog(0, $"Pallet:{palletcode}获取参数失败".Translated());
+ LogHelper.Instance.WriteLog($"Pallet:{palletcode}获取参数失败", "printLog");
+ return listRes;
+ }
+
+ MapPalletKvDataToPrintDict(pallet_kv_data, vdaPrintValues);
+ }
+ else
+ {
+ FillDebugPrintPalletData(vdaPrintValues);
+ }
+
+ //使用zera打印机
+ bool b = CommonMethods.palletPrint.write(CommonMethods.btwConfig.palletBTWPath, "pallet label", vdaPrintValues);
+ if (b)
+ {
+ listRes[0] = 1;
+ CommonMethods.AddDataMonitorLog(0, $"栈板码{palletcode}:打印VDA发送成功".Translated());
+ }
+ else
+ {
+ listRes[0] = 2;
+ CommonMethods.AddDataMonitorLog(0, $"栈板码{palletcode}:打印VDA发送失败".Translated());
+ LogHelper.Instance.WriteLog($"{palletcode}发送VDA失败", "printLog");
+ }
+
+
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError($"栈板打印线程异常:{ex}", "printLog");
+ }
+ return listRes;
+ }
+
+ ///
+ /// 将KV数据映射到打印字典(业务转换层)
+ ///
+ /// KV数据列表
+ /// 打印字典
+ private void MapPalletKvDataToPrintDict(List kvData, Dictionary printDict)
+ {
+ var kvDict = kvData.ToDictionary(kv => kv.tag_code, kv => kv.tag_value, StringComparer.OrdinalIgnoreCase);
+ foreach (var mapping in PalletFieldMappings)
+ {
+ if (kvDict.TryGetValue(mapping.Value, out var value))
+ {
+ printDict[mapping.Key] = value ?? string.Empty; // 空值处理
+ }
+ else
+ {
+ LogHelper.Instance.WriteLog($"托盘数据缺失字段:{mapping.Key}(对应TagCode:{mapping.Value})", "printLog");
+ printDict[mapping.Key] = string.Empty;
+ }
+ }
+ }
+
+ ///
+ /// 填充调试模式的测试数据(测试层)
+ ///
+ /// 打印字典
+ private void FillDebugPrintPalletData(Dictionary printDict)
+ {
+ printDict["customer"] = "57";
+ printDict["groupNumber"] = "88";
+ printDict["orderNumber"] = "19999";
+ printDict["packageQty"] = "10";
+ //printDict["产品描述"] = "方形铁锂";
+ printDict["productCode"] = "123";
+ printDict["palletQty"] = "2400";
+ printDict["palletNo"] = "1000";
+ printDict["remark"] = "测试";
+ }
+
+ ///
+ /// 保存栈板标签数据
+ ///
+ ///
+ private async Task SavePrintPalletAsync(string palletCode)
+ {
+ // 入参校验(避免空值写入数据库)
+ if (string.IsNullOrWhiteSpace(palletCode))
+ {
+ LogHelper.Instance.WriteLog($"包装箱[{palletCode}]对应的栈板码码为空");
+ }
+
+ // 明确指定为异步 lambda
+ try
+ {
+ var entity = new EGearEntity
+ {
+ PalletCode = palletCode,
+ TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ Remark = "栈板标签打印",
+ Result = "成功"
+ };
+
+
+ // 数据库操作
+ bool con_success = await CommonMethods.db.AddReturnBoolAsync(entity);
+
+ string logMsg = con_success
+ ? $"栈板[{palletCode}]标签信息存储成功"
+ : $"栈板[{palletCode}]标签信息存储失败";
+ CommonMethods.AddDataMonitorLog(0, logMsg);
+
+ var entityList = new List { entity };
+
+ CommonMethods.ShowPrintPalletDelegate?.Invoke(entityList);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Instance.WriteError(palletCode + $"栈板标签信息保存处理异常:{ex}", "SysErrorLog");
+ }
+
+ }
+ #endregion
+
+ //#region 3S 打印
+ /////
+ ///// 3S打印
+ /////
+ /////
+ /////
+ /////
+ //private async Task Print3SStation(JYResult bArrays, object plcModel)
+ //{
+ // PlcGroup pf = plcModel as PlcGroup;
+ // if (pf == null)
+ // {
+ // LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ // return;
+ // }
+
+ // if (CommonMethods.mesConfig.customerType != "O2")
+ // {
+ // // 重置PLC信号
+ // await Task.Run(() =>
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ // );
+ // CommonMethods.AddDataMonitorLog(0, "上位机不打印普通客户的3S标签.");
+ // return;
+ // }
+
+
+ // using (var timer = new TimedOperation("3S标签打印", (elapsedMs, operationName) =>
+ // {
+ // CommonMethods.AddDataMonitorLog(0, $"3S标签打印共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ // }))
+ // {
+ // if (bArrays.IsSuccess && bArrays.Content != null)
+ // {
+ // List listRes = GetList(1, 2);//默认为2,判断栈板码
+ // byte[] bytes = bArrays.Content.ByteReverse();
+ // string palletcode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, 39, System.Text.Encoding.ASCII).// D4610~4629
+ // Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ // if (String.IsNullOrEmpty(palletcode) || palletcode.Contains("ERROR") || palletcode.Contains("NG"))
+ // {
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//置位0
+ // CommonMethods.AddDataMonitorLog(0, "获取栈板码为空,请检查".Translated());
+ // return;
+ // }
+ // else
+ // {
+ // listRes = await Print3SLabel(palletcode, listRes);
+ // await SavePrint3SAsync(palletcode);
+
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//复位
+ // }
+
+ // }
+ // }
+ //}
+
+
+ //private async Task> Print3SLabel(string palletcode, List listRes)
+ //{
+
+ // //try
+ // //{
+ // // List pallet_kv_data = null;
+
+ // // pallet_kv_data = CommonMethods.db.QueryWhereList(it => it.pallet_id == palletcode);
+ // // if (pallet_kv_data == null || pallet_kv_data.Count == 0)
+ // // {
+ // // listRes[0] = 3;
+ // // CommonMethods.AddDataMonitorLog(0, $"Pallet:{palletcode}获取参数失败".Translated());
+ // // LogHelper.Instance.WriteLog($"Pallet:{palletcode}获取参数失败", "printLog");
+ // // return listRes;
+ // // }
+
+ // // // 定义需要写入的多组数据(变量名和值的键值对)
+ // // var palletLabelData = new Dictionary
+ // // {
+ // // //{ "vda1", VDAdata.receiver },
+ // // //{ "vda2", VDAdata.unloading },
+ // // //{ "vda4", VDAdata.supplier },
+ // // //{ "vda5", VDAdata.netWeight },
+ // // //{ "vda6", VDAdata.groWeight },
+ // // //{ "vda7", VDAdata.noBoxes },
+ // // //{ "vda8", VDAdata.partNo },//8数字+条形码(格式为39码)
+ // // //{ "vda8-1", VDAdata.bmwDmcJYA },//8-1 二维码
+ // // //{ "vda9", VDAdata.quantity },//9数字+条形码(格式为39码)
+ // // //{ "vda10", VDAdata.skuDescr },
+ // // //{ "vda11.1", VDAdata.sku },
+
+ // // // 可继续添加更多数据...
+ // // };
+
+ // // 初始化目标字典
+ // Dictionary vdaPrintValues = new Dictionary();
+ // // //循环处理每组数据
+ // // foreach (var entry in palletLabelData)
+ // // {
+ // // // 获取PLC变量信息
+ // // var plcVariable = getPLCVariable(entry.Key);
+ // // // 将BTName与值存入字典
+ // // vdaPrintValues[plcVariable.BTName] = entry.Value; // 使用索引器避免重复键异常(会覆盖旧值)
+ // // // 或者用 Add 方法(如果确保键不重复):
+ // // // vdaPrintValues.Add(plcVariable.BTName, entry.Value);
+ // // }
+ // //使用VDA打印机
+ // bool b = CommonMethods._3SPrint.write(CommonMethods.btwConfig.palletBTWPath, vdaPrintValues);
+ // if (b)
+ // {
+ // listRes[0] = 1;
+ // CommonMethods.AddDataMonitorLog(0, $"栈板码{palletcode}:打印VDA发送成功".Translated());
+ // }
+ // else
+ // {
+ // listRes[0] = 2;
+ // CommonMethods.AddDataMonitorLog(0, $"栈板码{palletcode}:打印VDA发送失败".Translated());
+ // LogHelper.Instance.WriteLog($"{palletcode}发送VDA失败", "printLog");
+ // }
+
+
+ // //}
+ // //catch (Exception ex)
+ // //{
+ // // LogHelper.Instance.WriteError($"栈板打印线程异常:{ex}", "printLog");
+ // //}
+ // return listRes;
+ //}
+
+ /////
+ ///// 保存3S标签数据
+ /////
+ /////
+ //private async Task SavePrint3SAsync(string palletCode)
+ //{
+ // // 入参校验(避免空值写入数据库)
+ // if (string.IsNullOrWhiteSpace(palletCode))
+ // {
+ // LogHelper.Instance.WriteLog($"包装箱[{palletCode}]对应的栈板码码为空");
+ // }
+
+ // // 明确指定为异步 lambda
+ // try
+ // {
+ // var entity = new EGearEntity
+ // {
+ // PalletCode = palletCode,
+ // TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ // Remark = "3S标签打印",
+ // Result = "成功"
+ // };
+
+
+ // // 数据库操作
+ // bool con_success = await CommonMethods.db.AddReturnBoolAsync(entity);
+
+ // string logMsg = con_success
+ // ? $"3S[{palletCode}]标签信息存储成功"
+ // : $"3S[{palletCode}]标签信息存储失败";
+ // CommonMethods.AddDataMonitorLog(0, logMsg);
+
+ // var entityList = new List { entity };
+
+ // CommonMethods.ShowPrintPalletDelegate?.Invoke(entityList);
+ // }
+ // catch (Exception ex)
+ // {
+ // LogHelper.Instance.WriteError(palletCode + $"3S标签信息保存处理异常:{ex}", "SysErrorLog");
+ // }
+
+ //}
+ //#endregion
+
+ //#region 6J 打印
+ /////
+ ///// 6J打印
+ /////
+ /////
+ /////
+ /////
+ //private async Task Print6JStation(JYResult bArrays, object plcModel)
+ //{
+ // PlcGroup pf = plcModel as PlcGroup;
+ // if (pf == null)
+ // {
+ // LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ // return;
+ // }
+
+ // if (CommonMethods.mesConfig.customerType != "O2")
+ // {
+ // // 重置PLC信号
+ // await Task.Run(() =>
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ // );
+ // CommonMethods.AddDataMonitorLog(0, "上位机不打印普通客户的6J标签.");
+ // return;
+ // }
+
+
+ // using (var timer = new TimedOperation("6J标签打印", (elapsedMs, operationName) =>
+ // {
+ // CommonMethods.AddDataMonitorLog(0, $"6J标签打印共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ // }))
+ // {
+ // if (bArrays.IsSuccess && bArrays.Content != null)
+ // {
+ // List listRes = GetList(1, 2);//默认为2,判断栈板码
+ // byte[] bytes = bArrays.Content.ByteReverse();
+ // string palletcode = StringLib.GetStringFromByteArrayByEncoding(bytes, 0, 39, System.Text.Encoding.ASCII).// D4610~4629
+ // Replace('\0', ' ').Replace('\r', ' ').Replace(" ", "").Trim();
+ // if (String.IsNullOrEmpty(palletcode) || palletcode.Contains("ERROR") || palletcode.Contains("NG"))
+ // {
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//置位0
+ // CommonMethods.AddDataMonitorLog(0, "获取栈板码为空,请检查".Translated());
+ // return;
+ // }
+ // else
+ // {
+ // listRes = await Print6JLabel(palletcode, listRes);
+ // await SavePrint6JAsync(palletcode);
+
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(pf.VarList[2].VarAddress, listRes.ToArray(), PLC.DataType.ArrShort);//判断结果写入PLC
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, (short)0);//复位
+ // }
+
+ // }
+ // }
+ //}
+
+
+ //private async Task> Print6JLabel(string palletcode, List listRes)
+ //{
+
+ // //try
+ // //{
+ // // List pallet_kv_data = null;
+
+ // // pallet_kv_data = CommonMethods.db.QueryWhereList(it => it.pallet_id == palletcode);
+ // // if (pallet_kv_data == null || pallet_kv_data.Count == 0)
+ // // {
+ // // listRes[0] = 3;
+ // // CommonMethods.AddDataMonitorLog(0, $"Pallet:{palletcode}获取参数失败".Translated());
+ // // LogHelper.Instance.WriteLog($"Pallet:{palletcode}获取参数失败", "printLog");
+ // // return listRes;
+ // // }
+
+ // // // 定义需要写入的多组数据(变量名和值的键值对)
+ // // var palletLabelData = new Dictionary
+ // // {
+ // // //{ "vda1", VDAdata.receiver },
+ // // //{ "vda2", VDAdata.unloading },
+ // // //{ "vda4", VDAdata.supplier },
+ // // //{ "vda5", VDAdata.netWeight },
+ // // //{ "vda6", VDAdata.groWeight },
+ // // //{ "vda7", VDAdata.noBoxes },
+ // // //{ "vda8", VDAdata.partNo },//8数字+条形码(格式为39码)
+ // // //{ "vda8-1", VDAdata.bmwDmcJYA },//8-1 二维码
+ // // //{ "vda9", VDAdata.quantity },//9数字+条形码(格式为39码)
+ // // //{ "vda10", VDAdata.skuDescr },
+ // // //{ "vda11.1", VDAdata.sku },
+
+ // // // 可继续添加更多数据...
+ // // };
+
+ // // 初始化目标字典
+ // Dictionary vdaPrintValues = new Dictionary();
+ // // //循环处理每组数据
+ // // foreach (var entry in palletLabelData)
+ // // {
+ // // // 获取PLC变量信息
+ // // var plcVariable = getPLCVariable(entry.Key);
+ // // // 将BTName与值存入字典
+ // // vdaPrintValues[plcVariable.BTName] = entry.Value; // 使用索引器避免重复键异常(会覆盖旧值)
+ // // // 或者用 Add 方法(如果确保键不重复):
+ // // // vdaPrintValues.Add(plcVariable.BTName, entry.Value);
+ // // }
+ // //使用VDA打印机
+ // bool b = CommonMethods._6JPrint.write(CommonMethods.btwConfig.palletBTWPath, vdaPrintValues);
+ // if (b)
+ // {
+ // listRes[0] = 1;
+ // CommonMethods.AddDataMonitorLog(0, $"栈板码{palletcode}:打印VDA发送成功".Translated());
+ // }
+ // else
+ // {
+ // listRes[0] = 2;
+ // CommonMethods.AddDataMonitorLog(0, $"栈板码{palletcode}:打印VDA发送失败".Translated());
+ // LogHelper.Instance.WriteLog($"{palletcode}发送VDA失败", "printLog");
+ // }
+
+
+ // //}
+ // //catch (Exception ex)
+ // //{
+ // // LogHelper.Instance.WriteError($"栈板打印线程异常:{ex}", "printLog");
+ // //}
+ // return listRes;
+ //}
+
+ /////
+ ///// 保存6J标签数据
+ /////
+ /////
+ //private async Task SavePrint6JAsync(string palletCode)
+ //{
+ // // 入参校验(避免空值写入数据库)
+ // if (string.IsNullOrWhiteSpace(palletCode))
+ // {
+ // LogHelper.Instance.WriteLog($"包装箱[{palletCode}]对应的栈板码码为空");
+ // }
+
+ // // 明确指定为异步 lambda
+ // try
+ // {
+ // var entity = new EGearEntity
+ // {
+ // PalletCode = palletCode,
+ // TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ // Remark = "3S标签打印",
+ // Result = "成功"
+ // };
+
+
+ // // 数据库操作
+ // bool con_success = await CommonMethods.db.AddReturnBoolAsync(entity);
+
+ // string logMsg = con_success
+ // ? $"3S[{palletCode}]标签信息存储成功"
+ // : $"3S[{palletCode}]标签信息存储失败";
+ // CommonMethods.AddDataMonitorLog(0, logMsg);
+
+ // var entityList = new List { entity };
+
+ // CommonMethods.ShowPrintPalletDelegate?.Invoke(entityList);
+ // }
+ // catch (Exception ex)
+ // {
+ // LogHelper.Instance.WriteError(palletCode + $"3S标签信息保存处理异常:{ex}", "SysErrorLog");
+ // }
+
+ //}
+
+ //#endregion
+
+ /////
+ ///// 绑定客户标签
+ /////
+ /////
+ /////
+ /////
+ //private async Task BindingLabelStation(JYResult bArrays, object plcModel)
+ //{
+ // PlcGroup pf = plcModel as PlcGroup;
+ // if (pf == null)
+ // {
+ // LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ // return;
+ // }
+ // if (CommonMethods.mesConfig.customerType != "O2")
+ // {
+ // // 重置PLC信号
+ // await Task.Run(() =>
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ // );
+ // CommonMethods.AddDataMonitorLog(0, "普通客户不用上传MES绑定标签");
+ // return;
+ // }
+
+ // using (var timer = new TimedOperation("标签绑定", (elapsedMs, operationName) =>
+ // {
+ // CommonMethods.AddDataMonitorLog(0, $"标签绑定共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ // }))
+ // {
+ // if (bArrays.IsSuccess && bArrays.Content != null)
+ // {
+ // await BindingLabelAsync(bArrays, plcModel);
+ // }
+
+ // // 重置PLC信号
+ // await Task.Run(() =>
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ // );
+ // }
+ //}
+
+ //private async Task BindingLabelAsync(JYResult bArrays, object plcModel)
+ //{
+ // PlcGroup pf = plcModel as PlcGroup;
+ // if (pf == null)
+ // {
+ // LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog");
+ // return;
+ // }
+
+ // using (var timer = new TimedOperation("标签绑定", (elapsedMs, operationName) =>
+ // {
+ // CommonMethods.AddDataMonitorLog(0, $"标签绑定共耗时:{operationName} 执行耗时:{elapsedMs}ms");
+ // }))
+ // {
+ // if (bArrays.IsSuccess && bArrays.Content != null)
+ // {
+ // try
+ // {
+ // #region 数据初始化
+ // //解析数据
+ // const int maxChannel = 8; // 固定8通道,改为常量便于维护
+ // var listMark = Enumerable.Repeat(false, maxChannel).ToList(); // 有料标志
+
+ // #endregion
+
+ // // 从D2021开始
+ // #region 数据解析
+ // byte[] bytes = bArrays.Content.ByteReverse();//高低位转换
+ // // TODO 从PLC读取出所有的电芯条码和条码位置!
+ // string containerCode = "";
+ // string palletCode = "";
+ // string custcartonCode = "";
+ // string custpalletCode = "";
+ // List custcarton = new List();
+ // List custpallet = new List();
+ // ////有料标志
+
+ // #endregion
+
+ // #region 上传MES
+ // //上传MES
+ // if (custcarton.Count > 0 && custpallet.Count > 0)
+ // {
+ // const int maxRetry = 3; // 最大重试3次
+ // const int retryIntervalMs = 1000; // 重试间隔1秒
+ // int retryCount = 0;
+ // //bool isSuccess = false;
+
+ // while (retryCount < maxRetry)
+ // {
+ // if (!CommonMethods.mesConfig.isConnected)
+ // {
+ // CommonMethods.AddDataMonitorLog(1, "MES连接中断,停止上传"); // 1:错误级别
+ // await WritePlcResult(pf, "D80", 1, PLC.DataType.Short); // 反馈错误
+ // break;
+ // }
+
+ // //Task<(bool success, string mesMessage, MesResType mesResType)>
+ // // BindingAsync( string containerCode, string palletCode, string custcartonCode, string custpalletCode, List custcarton, List custpallet)
+ // // 调用MES接口
+ // var (success, mesMessage, mesResType) = await CommonMethods.hbgMes.BindingAsync(
+ // CommonMethods.mesConfig.bindingUrl,
+ // CommonMethods.mesConfig.siteCode,
+ // CommonMethods.mesConfig.lineCode,
+ // CommonMethods.mesConfig.equipNum,
+ // containerCode, palletCode, custcartonCode, custpalletCode, custcarton, custpallet
+ // );
+
+ // if (success)
+ // {
+ // await WritePlcResult(pf, "D80", 0, PLC.DataType.Short); // 反馈成功
+ // break;
+ // }
+
+ // // 失败处理
+ // retryCount++;
+ // CommonMethods.AddDataMonitorLog(1, $"MES请求失败({retryCount}/{maxRetry}):{mesMessage}");
+ // if (retryCount >= maxRetry)
+ // {
+ // await WritePlcResult(pf, "D80", 1, PLC.DataType.Short); // 超过重试次数,反馈错误
+ // break;
+ // }
+ // await Task.Delay(retryIntervalMs); // 等待重试
+ // }
+ // }
+
+ // #endregion
+
+ // #region 数据反馈&保存
+
+
+ // // //读取结果显示
+ // // // TODO 界面上将本次mes获得档位的电芯码状态置为红色!存储各个档位的数据,监控每个档位的电芯是不是满了,满了就调用另一个上传mes接口处理,需要另外写一个监控线程来监控吗?
+
+ // try
+ // {
+ // //await SaveBindingDataAsync(custcarton); //是否需要存库?
+ // }
+ // catch (Exception ex)
+ // {
+ // //MessageBox.Show($"保存失败:{ex.Message}"); //TODO 是否需要弹窗提示?
+ // }
+
+
+ // // 重置PLC信号
+ // await Task.Run(() =>
+ // CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[0].VarAddress, 0)
+ // );
+
+ // #endregion
+
+ // }
+ // catch (Exception ex)
+ // {
+ // LogHelper.Instance.WriteError($"标签绑定线程处理异常:{ex}", "SysErrorLog");
+ // // 异常时反馈PLC错误
+ // await WritePlcResult(pf, "D80", 1, PLC.DataType.Short);
+ // }
+ // }
+ // }
+ //}
+
+ #region 保存箱唛信息数据
+ ///
+ /// 保存绑定信息数据
+ ///
+ ///
+
+ private async Task SaveBindingDataAsync(string containerCode, List list)
+ {
+ if (list == null || list.Count == 0)
+ return;
+
+ // 明确指定为异步 lambda
+ await Task.Run(async () =>
+ {
+ try
+ {
+ // 数据库操作
+ bool success = await CommonMethods.db.AddReturnBoolAsync