瀏覽代碼

Add project files.

Yuli 1 月之前
當前提交
fa8d08ef8a
共有 100 個文件被更改,包括 9329 次插入0 次删除
  1. 63 0
      .gitattributes
  2. 366 0
      .gitignore
  3. 25 0
      SKMC.API.sln
  4. 74 0
      SKMC.API/Client/Access/ClientAccess.cs
  5. 119 0
      SKMC.API/Client/Access/ClientAccessCodes.cs
  6. 42 0
      SKMC.API/Client/Access/ClientAccessConverter.cs
  7. 53 0
      SKMC.API/Client/Access/ClientRole.cs
  8. 93 0
      SKMC.API/Client/Access/IClientRoleAccesser.cs
  9. 152 0
      SKMC.API/Client/ClientCacher.cs
  10. 16 0
      SKMC.API/Client/Config/ClientConstants.cs
  11. 42 0
      SKMC.API/Client/Config/ClientException.cs
  12. 27 0
      SKMC.API/Client/Extension/IMouseTracer.cs
  13. 30 0
      SKMC.API/Client/Model/ClientMessage.cs
  14. 63 0
      SKMC.API/Client/Model/ClientNotification.cs
  15. 25 0
      SKMC.API/Client/UI/StyleManager.cs
  16. 42 0
      SKMC.API/Client/Views/ExceptionsViewModel.cs
  17. 20 0
      SKMC.API/Client/Views/IShowDialog.cs
  18. 36 0
      SKMC.API/Client/Views/MenuViewModel.cs
  19. 361 0
      SKMC.API/Common/CommonUtil.cs
  20. 35 0
      SKMC.API/Common/DB/Entity/BaseEntity.cs
  21. 17 0
      SKMC.API/Common/DB/Entity/ConfigEntity.cs
  22. 25 0
      SKMC.API/Common/DB/IModelConverter.cs
  23. 352 0
      SKMC.API/Common/DB/SqlSugarTool.cs
  24. 13 0
      SKMC.API/Common/Data/IDataFilter.cs
  25. 21 0
      SKMC.API/Common/Data/ITrendAnalyzer.cs
  26. 235 0
      SKMC.API/Common/Datetime/MicroTimer.cs
  27. 71 0
      SKMC.API/Common/Exceptions/ExceptionBase.cs
  28. 155 0
      SKMC.API/Common/Exceptions/ExceptionConfig.cs
  29. 66 0
      SKMC.API/Common/Exceptions/ExceptionShow.cs
  30. 60 0
      SKMC.API/Common/Expression/Predicator.cs
  31. 42 0
      SKMC.API/Common/File/AppConfig.cs
  32. 114 0
      SKMC.API/Common/File/IniFiles.cs
  33. 90 0
      SKMC.API/Common/File/XmlSerializerTool.cs
  34. 30 0
      SKMC.API/Common/Logger/ILogger.cs
  35. 55 0
      SKMC.API/Common/Logger/Log4netLogger.cs
  36. 29 0
      SKMC.API/Common/Logger/LogDataService.cs
  37. 50 0
      SKMC.API/Common/Logger/LogFactory.cs
  38. 35 0
      SKMC.API/Common/Logger/LoggingEventAppender.cs
  39. 40 0
      SKMC.API/Common/Logger/LoggingEventModel.cs
  40. 30 0
      SKMC.API/Common/Logger/LoggingEventTarget.cs
  41. 49 0
      SKMC.API/Common/Logger/NLogLogger.cs
  42. 37 0
      SKMC.API/Common/Monitor/BaseMonitor.cs
  43. 186 0
      SKMC.API/Common/Monitor/StateLatchManager.cs
  44. 89 0
      SKMC.API/Common/ObjectFactory.cs
  45. 24 0
      SKMC.API/Common/Tasks/TaskToken.cs
  46. 101 0
      SKMC.API/Common/Tasks/TaskTokener.cs
  47. 80 0
      SKMC.API/Common/Tasks/Tasks.cs
  48. 23 0
      SKMC.API/Common/Tcp/ITcpProxy.cs
  49. 42 0
      SKMC.API/Common/Tcp/ITcpServer.cs
  50. 96 0
      SKMC.API/Common/Tcp/TCPClient.cs
  51. 155 0
      SKMC.API/Common/Tcp/TcpClientBase.cs
  52. 52 0
      SKMC.API/Common/Types/BlockingQueue.cs
  53. 51 0
      SKMC.API/Common/Types/BoundedQueue.cs
  54. 245 0
      SKMC.API/Common/Types/ConcurrentList.cs
  55. 44 0
      SKMC.API/Common/Types/MessageQueue.cs
  56. 68 0
      SKMC.API/Common/Types/ObservableCollectionEx.cs
  57. 231 0
      SKMC.API/Common/Types/ObservableConcurrentList.cs
  58. 86 0
      SKMC.API/Common/Types/TimestampSet.cs
  59. 23 0
      SKMC.API/Device/Adapter/Vision/IResultParser.cs
  60. 141 0
      SKMC.API/Device/Adapter/Vision/SKVisionClient.cs
  61. 170 0
      SKMC.API/Device/Adapter/Vision/SKVisionClientBase.cs
  62. 159 0
      SKMC.API/Device/Adapter/Vision/SKVisionProtocol.cs
  63. 37 0
      SKMC.API/Device/Adapter/Vision/VisionException.cs
  64. 31 0
      SKMC.API/Device/Config/DeviceBaseConfig.cs
  65. 18 0
      SKMC.API/Device/Config/DeviceConfigStore.cs
  66. 28 0
      SKMC.API/Device/Config/DeviceConfiger.cs
  67. 16 0
      SKMC.API/Device/Config/DeviceConstants.cs
  68. 13 0
      SKMC.API/Device/Config/MachineConfigStore.cs
  69. 112 0
      SKMC.API/Device/Config/ParamEnum.cs
  70. 69 0
      SKMC.API/Device/DeviceCacher.cs
  71. 100 0
      SKMC.API/Device/Machine/IMachineBoardControl.cs
  72. 31 0
      SKMC.API/Device/Machine/IMachineButtonControl.cs
  73. 21 0
      SKMC.API/Device/Machine/IMachineTowerLightControl.cs
  74. 43 0
      SKMC.API/Device/Machine/MachineStatus.cs
  75. 64 0
      SKMC.API/Device/Machine/MachineStatusEnum.cs
  76. 314 0
      SKMC.API/Device/Material/Tray/Tray.cs
  77. 172 0
      SKMC.API/Device/Material/Tray/TrayConfig.cs
  78. 146 0
      SKMC.API/Device/Material/Tray/TraySlot.cs
  79. 35 0
      SKMC.API/Loader.cs
  80. 111 0
      SKMC.API/Motion/Config/MotionConfigStore.cs
  81. 52 0
      SKMC.API/Motion/Config/MotionConstants.cs
  82. 70 0
      SKMC.API/Motion/Config/MotionException.cs
  83. 57 0
      SKMC.API/Motion/Control/IMotionChecker.cs
  84. 624 0
      SKMC.API/Motion/Control/IMotionControl.cs
  85. 384 0
      SKMC.API/Motion/Driver/IMotionDriver.cs
  86. 82 0
      SKMC.API/Motion/Driver/IMotionDriverAdvance.cs
  87. 48 0
      SKMC.API/Motion/Driver/IMotionDriverManager.cs
  88. 47 0
      SKMC.API/Motion/Driver/IMotionDriverParser.cs
  89. 146 0
      SKMC.API/Motion/Model/MotionAO.cs
  90. 383 0
      SKMC.API/Motion/Model/MotionAxis.cs
  91. 65 0
      SKMC.API/Motion/Model/MotionAxisParam.cs
  92. 26 0
      SKMC.API/Motion/Model/MotionAxisRecord.cs
  93. 153 0
      SKMC.API/Motion/Model/MotionAxisStatus.cs
  94. 35 0
      SKMC.API/Motion/Model/MotionCnd.cs
  95. 236 0
      SKMC.API/Motion/Model/MotionIO.cs
  96. 70 0
      SKMC.API/Motion/Model/MotionIODev.cs
  97. 21 0
      SKMC.API/Motion/Model/MotionIOGroup.cs
  98. 33 0
      SKMC.API/Motion/Model/MotionPdo.cs
  99. 26 0
      SKMC.API/Motion/Model/MotionPosition.cs
  100. 44 0
      SKMC.API/Motion/Model/MotionSdo.cs

+ 63 - 0
.gitattributes

@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain

+ 366 - 0
.gitignore

@@ -0,0 +1,366 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-Code files
+!*.cs
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd

+ 25 - 0
SKMC.API.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.33130.400
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKMC.API", "SKMC.API\SKMC.API.csproj", "{D4B3EC9F-7DAD-45D7-A3E8-B8D7C664B9E9}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D4B3EC9F-7DAD-45D7-A3E8-B8D7C664B9E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D4B3EC9F-7DAD-45D7-A3E8-B8D7C664B9E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D4B3EC9F-7DAD-45D7-A3E8-B8D7C664B9E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D4B3EC9F-7DAD-45D7-A3E8-B8D7C664B9E9}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {316C2269-EB9D-4771-BE8A-D2D2519E3E20}
+	EndGlobalSection
+EndGlobal

+ 74 - 0
SKMC.API/Client/Access/ClientAccess.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Access
+{
+    /// <summary>
+    /// 客户端权限
+    /// </summary>
+    public class ClientAccess
+    {
+        private long _id;
+
+        public long Id
+        {
+            get { return _id; }
+            set { _id = value; }
+        }
+
+        private string _code;
+
+        /// <summary>
+        /// 权限码
+        /// </summary>
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; }
+        }
+
+        private string _name;
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; }
+        }
+
+        private long _parentId;
+
+        /// <summary>
+        /// 父节点Id(预留)
+        /// </summary>
+        public long ParentId
+        {
+            get { return _parentId; }
+            set { _parentId = value; }
+        }
+
+        private short _type;
+
+        /// <summary>
+        /// 权限类型, 0为UI类可见; 1为操作类可用
+        /// </summary>
+        public short Type
+        {
+            get { return _type; }
+            set { _type = value; }
+        }
+
+        /// <summary>
+        /// 是否管理类权限, 0为非管理类权限; 0为管理类权限
+        /// </summary>
+        public short Admin { get; set; }
+
+        /// <summary>
+        /// 当前权限在这些设备状态下时有效
+        /// </summary>
+        public byte[] DeviceStatus { get; set; }
+
+    }
+}

+ 119 - 0
SKMC.API/Client/Access/ClientAccessCodes.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Access
+{
+    /// <summary>
+    /// 权限列表 - 配合数据表SKMC_CLIENT_ACCESS
+    /// </summary>
+    public class ClientAccessCodes
+    {
+        #region 普通权限
+        /// <summary>
+        /// 主界面可视
+        /// </summary>
+        public static string MainView = "MainView";
+        /// <summary>
+        /// 主界面可操作
+        /// </summary>
+        public static string MainOper = "MainOper";
+        /// <summary>
+        /// 电气界面可视
+        /// </summary>
+        public static string ElectricView = "ElectricView";
+        /// <summary>
+        /// 电机界面可视
+        /// </summary>
+        public static string MotionAxisView = "MotionAxisView";
+        /// <summary>
+        /// 电机界面可操作
+        /// </summary>
+        public static string MotionAxisOper = "MotionAxisOper";
+        /// <summary>
+        /// IO界面可视
+        /// </summary>
+        public static string MotionIOView = "MotionIOView";
+        /// <summary>
+        /// IO界面可操作
+        /// </summary>
+        public static string MotionDoOper = "MotionDoOper";
+        /// <summary>
+        /// 设备基础配置界面可视
+        /// </summary>
+        public static string DeviceConfigView = "DeviceConfigView";
+        /// <summary>
+        /// 流程界面可视
+        /// </summary>
+        public static string ProcessView = "ProcessView";
+        /// <summary>
+        /// 流程参数界面可视
+        /// </summary>
+        public static string ProcessBaseConfigView = "ProcessBaseConfigView";
+        /// <summary>
+        /// 流程参数界面可操作
+        /// </summary>
+        public static string ProcessBaseConfigOper = "ProcessBaseConfigOper";
+        /// <summary>
+        /// 流程高级参数界面可视
+        /// </summary>
+        public static string ProcessAdvConfigView = "ProcessAdvConfigView";
+        /// <summary>
+        /// 流程高级参数界面可操作
+        /// </summary>
+        public static string ProcessAdvConfigOper = "ProcessAdvConfigOper";
+        /// <summary>
+        /// 流程点位界面可视
+        /// </summary>
+        public static string ProcessPointView = "ProcessPointView";
+        /// <summary>
+        /// 流程点位界面可操作
+        /// </summary>
+        public static string ProcessPointOper = "ProcessPointOper";
+        /// <summary>
+        /// 速度设置界面可视
+        /// </summary>
+        public static string ProcessSpeedView = "ProcessSpeedView";
+        /// <summary>
+        /// 速度设置界面可操作
+        /// </summary>
+        public static string ProcessSpeedOper = "ProcessSpeedOper";
+        /// <summary>
+        /// 手动界面可视
+        /// </summary>
+        public static string MannualView = "MannualView";
+        /// <summary>
+        /// 手动界面可操作
+        /// </summary>
+        public static string MannualOper = "MannualOper";
+        /// <summary>
+        /// 调试界面可视
+        /// </summary>
+        public static string DevelopView = "DevelopView";
+        /// <summary>
+        /// 调试界面可操作
+        /// </summary>
+        public static string DevelopOper = "DevelopOper";
+        /// <summary>
+        /// 生产记录界面可视
+        /// </summary>
+        public static string RecordView = "RecordView";
+        /// <summary>
+        /// 生产记录界面可操作
+        /// </summary>
+        public static string RecordOper = "RecordOper";
+        #endregion
+
+        #region 管理权限
+        public static string SystemManageView = "SystemManageView";
+
+        public static string SystemManageOper = "SystemManageOper";
+
+        public static string AccessManageView = "AccessManageView";
+
+        public static string AccessManageOper = "AccessManageOper";
+        #endregion
+    }
+}

+ 42 - 0
SKMC.API/Client/Access/ClientAccessConverter.cs

@@ -0,0 +1,42 @@
+using SKMC.Api.Common;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SKMC.Api.Client.Access
+{
+    /// <summary>
+    /// 权限码 -> 是否可见的转换器
+    /// </summary>
+    public class ClientAccessConverter : IMultiValueConverter
+    {
+        private readonly IClientRoleAccesser clientRoleAccesser;
+
+        public ClientAccessConverter()
+        {
+            clientRoleAccesser = ObjectFactory.Resolve<IClientRoleAccesser>();
+        }
+
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (values[0] is string accessCode)
+            {
+                return clientRoleAccesser.HasAccess(accessCode)
+                    ? Visibility.Visible
+                    : Visibility.Collapsed;
+            }
+            return Visibility.Collapsed;
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotSupportedException();
+        }
+
+    }
+}

+ 53 - 0
SKMC.API/Client/Access/ClientRole.cs

@@ -0,0 +1,53 @@
+using Prism.Mvvm;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace SKMC.Api.Client.Access
+{
+    /// <summary>
+    /// 客户端角色
+    /// </summary>
+    public class ClientRole : BindableBase
+    {
+
+        private long _id;
+
+        public long Id
+        {
+            get { return _id; }
+            set { _id = value; }
+        }
+
+        private string _code;
+
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; }
+        }
+
+        private string _name;
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; }
+        }
+
+        private string _passowrd;
+
+        public string Password
+        {
+            get { return _passowrd; }
+            set { _passowrd = value; }
+        }
+
+        public string ShowName
+        {
+            get { return $"{_name}  {_code}"; }
+        }
+
+        public ObservableCollection<ClientAccess> ClientAccesses { get; set; } = new ObservableCollection<ClientAccess>();
+
+    }
+}

+ 93 - 0
SKMC.API/Client/Access/IClientRoleAccesser.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Access
+{
+    /// <summary>
+    /// 角色权限访问接口
+    /// </summary>
+    public interface IClientRoleAccesser
+    {
+        /// <summary>
+        /// 当前角色
+        /// </summary>
+        ClientRole UserRole { get; set; }
+
+        /// <summary>
+        /// 权限标识, 切换权限时变更
+        /// </summary>
+        int AccessToken { get; set; }
+
+        /// <summary>
+        /// 所有角色列表(下拉选择)
+        /// </summary>
+        ObservableCollection<ClientRole> ClientRoles { get; set; }
+
+        /// <summary>
+        /// 角色登录
+        /// </summary>
+        /// <param name="roleCode">角色码</param>
+        /// <param name="password">登录密码</param>
+        /// <param name="action">后置动作</param>
+        /// <returns></returns>
+        bool Login(string roleCode, string password, Action action = null);
+
+        /// <summary>
+        /// 当前角色登出
+        /// </summary>
+        /// <param name="action">后置动作</param>
+        void Logout(Action action = null);
+
+        /// <summary>
+        /// 密码验证
+        /// </summary>
+        /// <param name="roleCode">角色码</param>
+        /// <param name="password">登录密码</param>
+        /// <param name="action">后置动作</param>
+        /// <returns></returns>
+        bool CheckPassword(string roleCode, string password, Action action = null);
+
+        /// <summary>
+        /// 更改密码
+        /// </summary>
+        /// <param name="roleCode">角色码</param>
+        /// <param name="password">登录密码</param>
+        /// <param name="newpwd">新密码</param>
+        void ChangePassword(string roleCode, string password, string newpwd);
+
+        /// <summary>
+        /// 权限验证
+        /// </summary>
+        /// <param name="accessCode"></param>
+        /// <returns></returns>
+        bool HasAccess(string accessCode);
+
+        /// <summary>
+        /// 权限验证, 并根据设备状态匹配
+        /// </summary>
+        /// <param name="accessCode"></param>
+        /// <returns></returns>
+        bool HasAccess(string accessCode, byte deviceStatus);
+
+        /// <summary>
+        /// 加载角色(权限)
+        /// </summary>
+        /// <param name="roleCode">角色码, null表示初始角色</param>
+        /// <returns></returns>
+        ClientRole LoadRole(string roleCode = null, Action action = null);
+
+        /// <summary>
+        /// 加载角色的事件
+        /// </summary>
+        event Action LoadRoleEvent;
+
+        /// <summary>
+        /// 卸载角色(权限)
+        /// </summary>
+        void UnloadRole(Action action = null);
+    }
+}

+ 152 - 0
SKMC.API/Client/ClientCacher.cs

@@ -0,0 +1,152 @@
+using Prism.Mvvm;
+using Prism.Regions;
+using Prism.Services.Dialogs;
+using SKMC.Api.Client.Model;
+using SKMC.Api.Client.Access;
+using SKMC.Api.Client.Views;
+using SKMC.Api.Common.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client
+{
+    /// <summary>
+    /// 客户端缓存器
+    /// </summary>
+    public abstract class ClientCacher : BindableBase
+    {
+        /// <summary>
+        /// UI的区域管理器
+        /// </summary>
+        public IRegionManager RegionManager { get; set; }
+
+        /// <summary>
+        /// UI的对话框管理器
+        /// </summary>
+        public IDialogService DialogService { get; set; }
+
+        /// <summary>
+        /// 角色权限访问接口
+        /// </summary>
+        public IClientRoleAccesser ClientRoleAccesser { get; set; }
+
+        /// <summary>
+        /// 异常窗口的抽象视图模型
+        /// </summary>
+        public ExceptionsViewModel ExceptionsViewModel { get; set; }
+
+        /// <summary>
+        /// 当前登录角色
+        /// </summary>
+        public string CurrentRole { get; set; }
+
+        /// <summary>
+        /// 当前选择的分类
+        /// </summary>
+        public string CurrentCatalog { get; set; }
+
+        /// <summary>
+        /// 分类更改事件
+        /// </summary>
+        public event Action<string> CatalogChanged;
+
+        /// <summary>
+        /// 分类更改动作
+        /// </summary>
+        /// <param name="catalog"></param>
+        public void CatalogChange(string catalog)
+        {
+            CurrentCatalog = catalog;
+            CatalogChanged?.Invoke(catalog);
+        }
+
+        //private string _selectView;
+
+        //public string SelectView
+        //{
+        //    get { return _selectView; }
+        //    set { _selectView = value; RaisePropertyChanged(); }
+        //}
+
+
+        /// <summary>
+        /// 客户端消息集
+        /// </summary>
+        public ObservableCollection<ClientMessage> ClientMessages { get; set; } = new ObservableCollection<ClientMessage>();
+
+        /// <summary>
+        /// 当前未处理的异常
+        /// </summary>
+        public ObservableCollection<ExceptionShow> CurrentExceptions { get; set; } = new ObservableCollection<ExceptionShow>();
+
+        /// <summary>
+        /// 历史异常记录
+        /// </summary>
+        public ObservableCollection<ExceptionShow> HistoryExceptions { get; set; } = new ObservableCollection<ExceptionShow>();
+
+        /// <summary>
+        /// 已弹窗的提醒对话框
+        /// </summary>
+        public List<IShowDialog> ShowCommonDialogs = new List<IShowDialog>();
+
+        /// <summary>
+        /// 已弹窗的异常对话框
+        /// </summary>
+        public List<IShowDialog> ShowExceptionDialogs = new List<IShowDialog>();
+
+        /// <summary>
+        /// 界面跳转
+        /// </summary>
+        /// <param name="view"></param>
+        public abstract void Forward(string view);
+
+        /// <summary>
+        /// 弹出通用的对话框
+        /// </summary>
+        /// <param name="viewName">UI的view层显示名称</param>
+        /// <param name="parms">参数表</param>
+        /// <param name="callback">回调函数</param>
+        /// <param name="blocked">是否阻塞控制流程</param>
+        public abstract string ShowCommonDialog(string viewName, DialogParameters parms, Action<IDialogResult> callback, bool blocked = true);
+
+        /// <summary>
+        /// 弹出异常对话框
+        /// </summary>
+        /// <param name="viewName">UI的view层显示名称</param>
+        /// <param name="exception">异常对象</param>
+        /// <param name="blocked">是否阻塞控制流程</param>
+        public abstract string ShowExceptionDialog(string viewName, ExceptionShow exception, bool blocked = true);
+
+        /// <summary>
+        /// 查询弹窗是否显示
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        public abstract bool IsDialogShow(string id);
+
+        /// <summary>
+        /// 关闭指定id的对话框(通知或异常)
+        /// </summary>
+        /// <param name="id"></param>
+        public abstract void CloseDialog(string id);
+
+        /// <summary>
+        /// 关闭所有已弹出的对话框(通知或异常)
+        /// </summary>
+        public abstract void CloseAllDialogs();
+
+        /// <summary>
+        /// 清除当前异常 (转移到历史异常中)
+        /// </summary>
+        public void ClearExceptions()
+        {
+            HistoryExceptions.AddRange(CurrentExceptions);
+            CurrentExceptions.Clear();
+        }
+
+    }
+}

+ 16 - 0
SKMC.API/Client/Config/ClientConstants.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Config
+{
+    public class ClientConstants
+    {
+        public const string Operation_Retry = "重试";
+        public const string Operation_Ignore = "忽略";
+        public const string Operation_Abort = "退出";
+        public const string Catalog_ALL = "ALL";
+    }
+}

+ 42 - 0
SKMC.API/Client/Config/ClientException.cs

@@ -0,0 +1,42 @@
+using SKMC.Api.Common.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Config
+{
+    public class ClientException : ExceptionBase
+    {
+        /// <summary>
+        /// 系统运行异常 2048
+        /// </summary>
+        public static int Runtime_Error = 0b_1000_0000_0000;
+
+        /// <summary>
+        /// 数据库连接失败 2049
+        /// </summary>
+        public static int OpenDB_Fail = 0b_1000_0000_0001;
+
+        /// <summary>
+        /// 加载配置文件失败 2050
+        /// </summary>
+        public static int LoadCfg_Fail = 0b_1000_0000_0010;
+
+        /// <summary>
+        /// 打开控制卡失败 2064
+        /// </summary>
+        public static int OpenCard_Fail = 0b_1000_0001_0000;
+
+        /// <summary>
+        /// 初始化控制卡失败 2066
+        /// </summary>
+        public static int InitCard_Fail = 0b_1000_0001_0010;
+
+        /// <summary>
+        /// 连接控制卡总线失败 2068
+        /// </summary>
+        public static int ConnCard_Fail = 0b_1000_0001_0100;
+    }
+}

+ 27 - 0
SKMC.API/Client/Extension/IMouseTracer.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Extension
+{
+    /// <summary>
+    /// 鼠标轨迹监测服务
+    /// 如果用户超时未使用则调用动作委托
+    /// </summary>
+    public interface IMouseTracer
+    {
+        /// <summary>
+        /// 启动监测
+        /// </summary>
+        /// <param name="period">监测间隔时间, 推荐2000毫秒</param>
+        /// <param name="timeout">监测超时时间,根据需要设置,单位毫秒</param>
+        void Start(int period, int timeout);
+
+        /// <summary>
+        /// 停止监测
+        /// </summary>
+        void Stop();
+    }
+}

+ 30 - 0
SKMC.API/Client/Model/ClientMessage.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Model
+{
+    /// <summary>
+    /// 客户端消息
+    /// </summary>
+    public class ClientMessage
+    {
+        /// <summary>
+        /// 消息时间
+        /// </summary>
+        public DateTime DateTime { get; set; }
+
+        /// <summary>
+        /// 消息内容
+        /// </summary>
+        public string Message { get; set; }
+
+        /// <summary>
+        /// 消息级别, 0:普通  1:重要
+        /// </summary>
+        public short Level { get; set; }
+
+    }
+}

+ 63 - 0
SKMC.API/Client/Model/ClientNotification.cs

@@ -0,0 +1,63 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Model
+{
+    /// <summary>
+    /// 客户端UI提醒, 提供3个级别对应3个背景色
+    /// </summary>
+    public class ClientNotification : BindableBase
+    {
+        private string _head;
+
+        /// <summary>
+        /// 主标题
+        /// </summary>
+        public string Head
+        {
+            get { return _head; }
+            set { _head = value; RaisePropertyChanged(); }
+        }
+
+        private string _detail;
+
+        /// <summary>
+        /// 详情
+        /// </summary>
+        public string Detail
+        {
+            get { return _detail; }
+            set { _detail = value; RaisePropertyChanged(); }
+        }
+
+        private DateTime _dateTime;
+
+        /// <summary>
+        /// 发生时间
+        /// </summary>
+        public DateTime DateTime
+        {
+            get { return _dateTime; }
+            set { _dateTime = value; RaisePropertyChanged(); }
+        }
+
+        private byte _level;
+
+        /// <summary>
+        /// 提醒级别 0:普通提醒(浅蓝色背景) 1: 重要提醒(黄色背景) 2:紧急提醒(红色背景)
+        /// </summary>
+        public byte Level
+        {
+            get { return _level; }
+            set { _level = value; RaisePropertyChanged(); }
+        }
+
+
+        public Action ConfirmAction { get; set; }
+
+    }
+}

+ 25 - 0
SKMC.API/Client/UI/StyleManager.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace SKMC.Api.Client.UI
+{
+    public class StyleManager
+    {
+        /// <summary>
+        /// 加载全局style配置文件 (可应用于SKMC.Client内的界面组件)
+        /// </summary>
+        /// <param name="path"></param>
+        public static void LoadStyle(string path)
+        {
+            ResourceDictionary dict = new ResourceDictionary
+            {
+                Source = new Uri(path, UriKind.RelativeOrAbsolute)
+            };
+            Application.Current.Resources.MergedDictionaries.Add(dict);
+        }
+    }
+}

+ 42 - 0
SKMC.API/Client/Views/ExceptionsViewModel.cs

@@ -0,0 +1,42 @@
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Views
+{
+    /// <summary>
+    /// 异常窗口的抽象视图模型
+    /// </summary>
+    public class ExceptionsViewModel : BindableBase
+    {
+        /// <summary>
+        /// 异常对话框是否已弹出
+        /// </summary>
+        public bool IsExceptionsOpened { get; set; }
+
+        /// <summary>
+        /// 恢复运行是否可用
+        /// </summary>
+        public virtual bool IsResumeEnabled { get; set; }
+
+        /// <summary>
+        /// 一键重试命令
+        /// </summary>
+        public virtual DelegateCommand RetryAllCommand { get; set; }
+
+        /// <summary>
+        /// 恢复运行命令
+        /// </summary>
+        public virtual DelegateCommand ResumeRunCommand { get; set; }
+
+        /// <summary>
+        /// 退出运行命令
+        /// </summary>
+        public virtual DelegateCommand QuitRunCommand { get; set; }
+    }
+}

+ 20 - 0
SKMC.API/Client/Views/IShowDialog.cs

@@ -0,0 +1,20 @@
+using Prism.Services.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Views
+{
+    /// <summary>
+    /// 可外部关闭的Dialog接口
+    /// </summary>
+    public interface IShowDialog : IDialogAware
+    {
+
+        string Id { get; set; }
+
+        void CloseDialog();
+    }
+}

+ 36 - 0
SKMC.API/Client/Views/MenuViewModel.cs

@@ -0,0 +1,36 @@
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Regions;
+using SKMC.Api.Client.Access;
+using SKMC.Api.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Client.Views
+{
+    /// <summary>
+    /// 菜单项VM
+    /// </summary>
+    public class MenuViewModel : BindableBase
+    {
+        protected readonly IRegionManager regionManager;
+        protected readonly NavigationParameters parameters = new NavigationParameters();
+
+        public DelegateCommand<string> OpenCommand { get; private set; }
+
+        public IClientRoleAccesser ClientRoleAccesser { get; set; } = ObjectFactory.Resolve<IClientRoleAccesser>();
+
+        public MenuViewModel(IRegionManager regionManager)
+        {
+            this.regionManager = regionManager;
+            OpenCommand = new DelegateCommand<string>(Forward);
+        }
+
+        protected virtual void Forward(string obj)
+        {
+        }
+    }
+}

+ 361 - 0
SKMC.API/Common/CommonUtil.cs

@@ -0,0 +1,361 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace SKMC.Api.Common
+{
+    /// <summary>
+    /// 字符串等数值变换工具类
+    /// </summary>
+    public class CommonUtil
+    {
+        /// <summary>
+        /// 创建一个基于Guid的long类型Id
+        /// </summary>
+        /// <returns></returns>
+        public static long LongId()
+        {
+            byte[] guidBytes = Guid.NewGuid().ToByteArray();
+            return BitConverter.ToInt64(guidBytes, 0) & long.MaxValue;
+        }
+
+        /// <summary>
+        /// 生成一个基于Guid连续字符的字符串Id
+        /// </summary>
+        /// <returns></returns>
+        public static string StringId()
+        {
+            return Guid.NewGuid().ToString("N");
+        }
+
+        /// <summary>
+        /// String类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <returns></returns>
+        public static string GetStringValue(object value)
+        {
+            return GetStringValue(value, string.Empty);
+        }
+
+        /// <summary>
+        /// String类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <param name="defaultValue">默认值</param>
+        /// <returns></returns>
+        public static string GetStringValue(object value, string defaultValue)
+        {
+            if (Convert.IsDBNull(value))
+            {
+                return defaultValue;
+            }
+            else if (value == null)
+            {
+                return defaultValue;
+            }
+            else
+            {
+                return value.ToString();
+            }
+        }
+
+        /// <summary>
+        /// Bool类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <returns></returns>
+        public static bool GetBoolValue(string value)
+        {
+            bool bValue;
+            Boolean.TryParse(value, out bValue);
+            return bValue;
+        }
+
+        /// <summary>
+        /// Int类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <returns></returns>
+        public static int GetIntValue(object value)
+        {
+            return CommonUtil.GetIntValue(value, 0);
+        }
+        /// <summary>
+        /// Int类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <param name="defaultValue">默认值</param>
+        /// <returns></returns>
+        public static int GetIntValue(object value, int defaultValue)
+        {
+            if (Convert.IsDBNull(value))
+            {
+                return defaultValue;
+            }
+            else if (value == null)
+            {
+                return defaultValue;
+            }
+
+            int result;
+            if (int.TryParse(value.ToString(), out result) == true)
+            {
+                return result;
+            }
+            else
+            {
+                return defaultValue;
+            }
+        }
+
+        /// <summary>
+        /// Float类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <returns></returns>
+        public static float GetFloatValue(object value)
+        {
+            return GetFloatValue(value, 0);
+        }
+
+        /// <summary>
+        /// Float类型変換
+        /// </summary>
+        /// <param name="data">目标对象</param>
+        /// <returns></returns>
+        public static float GetNotNullFloatValue(Nullable<float> data)
+        {
+            if (data != null)
+            {
+                return GetFloatValue(data.Value, 0);
+            }
+            else
+            {
+                return 0;
+            }
+        }
+        /// <summary>
+        /// Float类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <param name="defaultValue">默认值</param>
+        /// <returns></returns>
+        public static float GetFloatValue(object value, float defaultValue)
+        {
+            if (Convert.IsDBNull(value))
+            {
+                return defaultValue;
+            }
+            else if (value == null)
+            {
+                return defaultValue;
+            }
+
+            float result;
+            if (float.TryParse(value.ToString(), out result) == true)
+            {
+                return result;
+            }
+            else
+            {
+                return defaultValue;
+            }
+        }
+
+        /// <summary>
+        /// 比较两个字符串类型的值是否相同
+        /// </summary>
+        /// <param name="value1">字符串1</param>
+        /// <param name="value2">字符串2</param>
+        /// <returns></returns>
+        public static bool StringCompare(string value1, string value2)
+        {
+            bool result;
+            if ((value1 == null) && (value2 == null))
+            {
+                result = true;
+            }
+            if ((value1 == null) || (value2 == null))
+            {
+                result = false;
+            }
+            else
+            {
+                result = value1.Equals(value2);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// 确认指定的字符串是否为空
+        /// </summary>
+        /// <param name="value">対象文字列</param>
+        /// <returns></returns>
+        public static bool IsEmptyString(string value)
+        {
+            if (value == null)
+            {
+                return true;
+            }
+            if (StringCompare(value.Trim(), string.Empty) == true)
+            {
+                return true;
+            }
+            return false;
+        }
+
+        public static bool IsEmptyList<T>(IEnumerable<T> source)
+        {
+            if (source == null) return true;
+            return !source.Any();
+        }
+
+        /// <summary>
+        /// 小数标记验证
+        /// </summary>
+        /// <param name="target">验证对象</param>
+        /// <param name="intDigit">整数位数</param>
+        /// <param name="decDigit">小数位数</param>
+        /// <returns></returns>
+        public static bool IsDecimalNumber(object target, int intDigit, int decDigit)
+        {
+            bool rlt = false;
+
+            if (intDigit > 0 && decDigit > 0 && IsDecimal(GetStringValue(target)))
+            {
+                StringBuilder regularExp = new StringBuilder();
+                regularExp.Append("^\\-?[0-9]{1,");
+                regularExp.Append(intDigit);
+                regularExp.Append("}(\\.[0-9]{1,");
+                regularExp.Append(decDigit);
+                regularExp.Append("})?$");
+                if (Regex.IsMatch(target.ToString(), regularExp.ToString()))
+                {
+                    rlt = true;
+                }
+            }
+            return rlt;
+        }
+
+        /// <summary>
+        /// 是否为Decimal
+        /// </summary>
+        /// <param name="target"></param>
+        /// <returns></returns>
+        public static bool IsDecimal(string target)
+        {
+            bool result = false;
+            decimal decNum;
+
+            if (Decimal.TryParse(target, out decNum))
+            {
+                result = true;
+            }
+            else
+            {
+                result = false;
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// double类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <returns></returns>
+        public static double GetDoubleValue(object value)
+        {
+            return GetDoubleValue(value, 0);
+        }
+
+        /// <summary>
+        /// double类型変換
+        /// </summary>
+        /// <param name="value">目标对象</param>
+        /// <param name="defaultValue">默认值</param>
+        /// <returns></returns>
+        public static double GetDoubleValue(object value, double defaultValue)
+        {
+            if (Convert.IsDBNull(value))
+            {
+                return defaultValue;
+            }
+            else if (value == null)
+            {
+                return defaultValue;
+            }
+
+            double result;
+            if (double.TryParse(value.ToString(), out result) == true)
+            {
+                return result;
+            }
+            else
+            {
+                return defaultValue;
+            }
+        }
+
+        /// <summary>
+        /// 舍入小于或等于默认小数
+        /// </summary>
+        /// <param name="value">対象数值</param>
+        /// <param name="format">显示小数位数格式({0:##0.000})</param>
+        /// <returns>結果</returns>
+        public static float CutDecimalValue(float value, string format)
+        {
+            string strNewWithoutDec = string.Format(format, value);
+            return GetFloatValue(strNewWithoutDec);
+        }
+
+        /// <summary>
+        /// 将数字转换为Byte数组(翻转)
+        /// </summary>
+        /// <param name="data">数值数据</param>
+        /// <returns>byte数组</returns>
+        public static byte[] GetIntReversedByteValue(int data, int len)
+        {
+            byte[] inValue = BitConverter.GetBytes(data);
+            byte[] retValue = new byte[len];
+
+            for (int i = 0; i < len; i++)
+            {
+                retValue[i] = (i < inValue.Length) ? inValue[i] : (byte)0;
+            }
+            // 翻转
+            Array.Reverse(retValue);
+            return retValue;
+        }
+
+        /// <summary>
+        /// 将数字转换为Byte数组
+        /// </summary>
+        /// <param name="data">数值数据</param>
+        /// <returns>byte数组</returns>
+        public static byte[] GetIntByteValue(int data, int len)
+        {
+            byte[] inValue = BitConverter.GetBytes(data);
+            byte[] retValue = new byte[len];
+
+            for (int i = 0; i < len; i++)
+            {
+                retValue[i] = (i < inValue.Length) ? inValue[i] : (byte)0;
+            }
+            return retValue;
+        }
+
+        public static string PasswordEncryption(string pwd)
+        {
+            SHA1 sha1 = SHA1.Create();
+            byte[] originalPwd = Encoding.UTF8.GetBytes(pwd);
+            byte[] encryPwd = sha1.ComputeHash(originalPwd);
+            return string.Join("", encryPwd.Select(b => string.Format("{0:x2}", b)).ToArray()).ToUpper();
+        }
+    }
+}

+ 35 - 0
SKMC.API/Common/DB/Entity/BaseEntity.cs

@@ -0,0 +1,35 @@
+using SqlSugar;
+using System;
+
+namespace SKMC.Api.Common.DB.Entity
+{
+    /// <summary>
+    /// 基础的Entity模板
+    /// </summary>
+    public class BaseEntity
+    {
+        [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
+        public long Id { get; set; }
+
+        public string Oper { get; set; }
+
+        public DateTime CreateTime { get; set; }
+
+        public DateTime UpdateTime { get; set; }
+
+        public long Seq { get; set; }
+
+        public BaseEntity() 
+        {
+            if (Id == 0)
+            {
+                Id = BitConverter.ToInt64(System.Guid.NewGuid().ToByteArray(), 0);
+            }
+
+            if (Oper == null) Oper = "OP";
+            if (CreateTime == null) CreateTime = DateTime.Now;
+            if (UpdateTime == null) UpdateTime = DateTime.Now;
+            if (Seq == 0) Seq = CreateTime.Ticks;
+        }
+    }
+}

+ 17 - 0
SKMC.API/Common/DB/Entity/ConfigEntity.cs

@@ -0,0 +1,17 @@
+using SqlSugar;
+
+namespace SKMC.Api.Common.DB.Entity
+{
+    /// <summary>
+    /// 配置类型的Entity模板
+    /// </summary>
+    public class ConfigEntity : BaseEntity
+    {
+        public string Code { get; set; }
+
+        public string Name { get; set; }
+
+        [SugarColumn(IsNullable = true)]
+        public string Note { get; set; }
+    }
+}

+ 25 - 0
SKMC.API/Common/DB/IModelConverter.cs

@@ -0,0 +1,25 @@
+
+namespace SKMC.Api.Common.DB
+{
+    /// <summary>
+    /// Model与Entity的转换器接口
+    /// </summary>
+    /// <typeparam name="M">Model模型类</typeparam>
+    /// <typeparam name="T">Entity实体类</typeparam>
+    public interface IModelConverter<M, T>
+    {
+        /// <summary>
+        /// Model转Entity
+        /// </summary>
+        /// <param name="m">Model实例</param>
+        /// <returns>Entity实例</returns>
+        T ConvertMT(M m);
+
+        /// <summary>
+        /// Entity转Model
+        /// </summary>
+        /// <param name="t">Entity实例</param>
+        /// <returns>Model实例</returns>
+        M ConvertTM(T t);
+    }
+}

+ 352 - 0
SKMC.API/Common/DB/SqlSugarTool.cs

@@ -0,0 +1,352 @@
+using SKMC.Api.Common.File;
+using Newtonsoft.Json;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using DbType = SqlSugar.DbType;
+
+namespace SKMC.Api.Common.DB
+{
+    /// <summary>
+    /// SqlSugar的工具类
+    /// </summary>
+    public class SqlSugarTool
+    {
+        private static readonly ILogger log = LogFactory.Get();
+
+        private static readonly AppConfig appConfig = AppConfig.Instance();
+
+        /// <summary>
+        /// 从配置库文件获取数据库实例
+        /// </summary>
+        /// <returns></returns>
+        public static SqlSugarClient GetConfigDB() => GetDB("DB_CONFIG");
+
+        /// <summary>
+        /// 从运行库文件获取数据库实例
+        /// </summary>
+        /// <returns></returns>
+        public static SqlSugarClient GetRuntimeDB() => GetDB("DB_RUNDATA");
+
+        private static SqlSugarClient GetDB(string dbName)
+        {
+            SqlSugarClient db = new SqlSugarClient(new ConnectionConfig()
+            {
+                ConnectionString = String.Format(appConfig.ReadStringValue(dbName, "connection.datasource"), System.Environment.CurrentDirectory),
+                DbType = DbType.Sqlite,
+                // 自动释放数据务,如果存在事务,在事务结束后释放
+                IsAutoCloseConnection = appConfig.ReadBoolValue(dbName, "connection.autoclosed"),
+                // 从数据库系统表读取主键信息中(InitKeyType.Attribute从实体特性中读取主键自增列信息)
+                InitKeyType = InitKeyType.Attribute
+                // 暂不设置数据库缓存
+            });
+
+            //  调试SQL,AOP ,日志
+            db.Aop.OnLogExecuted = (sql, pars) => // SQL执行完事件
+            {
+                // 获取执行后的总毫秒数
+                double sqlExecutionTotalMilliseconds = db.Ado.SqlExecutionTime.TotalMilliseconds;
+            };
+            db.Aop.OnLogExecuting = (sql, pars) => // SQL执行前事件。可在这里查看生成的sql
+            {
+                string tempSQL = LookSQL(sql, pars);
+            };
+            db.Aop.OnError = (exp) =>// 执行SQL 错误事件
+            {
+                // exp.sql exp.parameters 可以拿到参数和错误Sql  
+                StringBuilder sb_SugarParameterStr = new StringBuilder("###SugarParameter参数为:");
+                var parametres = exp.Parametres as SugarParameter[];
+                if (parametres != null)
+                {
+                    sb_SugarParameterStr.Append(JsonConvert.SerializeObject(parametres));
+                }
+
+                StringBuilder sb_error = new StringBuilder();
+                sb_error.AppendLine("SqlSugarClient执行sql异常信息:" + exp.Message);
+                sb_error.AppendLine("###赋值后sql:" + LookSQL(exp.Sql, parametres));
+                sb_error.AppendLine("###带参数的sql:" + exp.Sql);
+                sb_error.AppendLine("###参数信息:" + sb_SugarParameterStr.ToString());
+                sb_error.AppendLine("###StackTrace信息:" + exp.StackTrace);
+
+                log.Error(sb_error.ToString());
+            };
+
+            db.Aop.OnExecutingChangeSql = (sql, pars) => // SQL执行前 可以修改SQL
+            {
+                // 判断update或delete方法是否有where条件。如果真的想删除所有数据,请where(p=>true)或where(p=>p.id>0)
+                if (sql.TrimStart().IndexOf("delete ", StringComparison.CurrentCultureIgnoreCase) == 0)
+                {
+                    if (sql.IndexOf("where", StringComparison.CurrentCultureIgnoreCase) <= 0)
+                    {
+                        throw new Exception("delete删除方法需要有where条件!!");
+                    }
+                }
+                else if (sql.TrimStart().IndexOf("update ", StringComparison.CurrentCultureIgnoreCase) == 0)
+                {
+                    if (sql.IndexOf("where", StringComparison.CurrentCultureIgnoreCase) <= 0)
+                    {
+                        throw new Exception("update更新方法需要有where条件!!");
+                    }
+                }
+
+                return new KeyValuePair<string, SugarParameter[]>(sql, pars);
+            };
+
+            // db.Aop.OnDiffLogEvent = it =>//  数据库操作前和操作后的数据变化。
+            // {
+            //     var editBeforeData = it.BeforeData;
+            //     var editAfterData = it.AfterData;
+            //     var sql = it.Sql;
+            //     var parameter = it.Parameters;
+            //     var data = it.BusinessData;
+            //     var time = it.Time;
+            //     var diffType = it.DiffType;// 枚举值 insert 、update 和 delete 用来作业务区分
+
+            //     日志方法
+            // };
+
+            // db.Ado.CommandTimeOut // 设置sql执行超时等待时间(毫秒单位)
+            // db.Ado.Connection.ConnectionTimeout // 设置数据库连接等待时间(毫秒单位)
+
+            return db;
+        }
+
+        ///<summary>
+        ///查看赋值后的sql
+        ///</summary>
+        ///<param name="sql"></param>
+        ///<param name="pars">参数</param>
+        ///<returns></returns>
+        private static string LookSQL(string sql, SugarParameter[] pars)
+        {
+            if (pars == null || pars.Length == 0) return sql;
+
+            StringBuilder sb_sql = new StringBuilder(sql);
+            var tempOrderPars = pars.Where(p => p.Value != null).OrderByDescending(p => p.ParameterName.Length).ToList();// 防止 @par1错误替换@par12
+            for (var index = 0; index < tempOrderPars.Count; index++)
+            {
+                sb_sql.Replace(tempOrderPars[index].ParameterName, "'" + tempOrderPars[index].Value.ToString() + "'");
+            }
+
+            return sb_sql.ToString();
+        }
+
+        /// <summary>
+        /// 获取一个实体
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="id">主键</param>
+        /// <param name="db"></param>
+        /// <returns></returns>
+        public static T GetModel<T>(object id, SqlSugarClient db)
+        {
+            return db.Queryable<T>().InSingle(id);
+        }
+
+        /// <summary>
+        /// 从缓存中获取一个实体
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="id">主键</param>
+        /// <param name="db"></param>
+        /// <returns></returns>
+        public static T GetModel_WithCache<T>(object id, SqlSugarClient db)
+        {
+            return db.Queryable<T>().WithCache().InSingle(id);
+        }
+
+
+        /// <summary>
+        /// 添加实体数据,并返回主键值(主键为自增int类型id,实体需要设置主键特性)(为null字段不更新,注意model有默认值的情况)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int InsertModel<T>(T t, SqlSugarClient db) where T : class, new()
+        {
+            // Where(true/*不插入null值的列*/,false/*不插入主键值*/)
+            return db.Insertable(t).
+                // IgnoreColumns(p=>p.Equals("id",StringComparison.InvariantCultureIgnoreCase)).
+                IgnoreColumns(true, false).ExecuteReturnIdentity();
+        }
+
+        /// <summary>
+        /// 添加实体数据,并返回主键值(主键为自增long类型id,实体需要设置主键特性)(为null字段不更新,注意model有默认值的情况)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static long InsertModel_BigIdentity<T>(T t, SqlSugarClient db) where T : class, new()
+        {
+            // Where(true/*不插入null值的列*/,false/*不插入主键值*/)
+            return db.Insertable(t).
+                // IgnoreColumns(p=>p.Equals("id",StringComparison.InvariantCultureIgnoreCase)).
+                IgnoreColumns(true, false).ExecuteReturnBigIdentity();
+        }
+
+        /// <summary>
+        /// 添加实体数据,删除缓存,并返回主键值(主键为自增int类型id,实体需要设置主键特性)(为null字段不更新,注意model有默认值的情况)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int InsertModel_RemoveDataCache<T>(T t, SqlSugarClient db) where T : class, new()
+        {
+            // Where(true/*不插入null值的列*/,false/*不插入主键值*/)
+            return db.Insertable(t).
+                // IgnoreColumns(p=>p.Equals("id",StringComparison.InvariantCultureIgnoreCase)).
+                IgnoreColumns(true, false).RemoveDataCache().ExecuteReturnIdentity();
+        }
+
+        /// <summary>
+        /// 添加实体数据,并返回主键值(主键为自增long类型id,实体需要设置主键特性)(为null字段不更新,注意model有默认值的情况)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static long InsertModel_BigIdentity_RemoveDataCache<T>(T t, SqlSugarClient db) where T : class, new()
+        {
+            // Where(true/*不插入null值的列*/,false/*不插入主键值*/)
+            return db.Insertable(t).
+                // IgnoreColumns(p=>p.Equals("id",StringComparison.InvariantCultureIgnoreCase)).
+                IgnoreColumns(true, false).RemoveDataCache().ExecuteReturnBigIdentity();
+        }
+
+        /// <summary>
+        /// 根据主键更新实体,返回影响条数(实体字段要有主键特性)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int UpdateModelByKey<T>(T t, SqlSugarClient db) where T : class, new()
+        {
+            // 字段null,不进行更新
+            return db.Updateable(t).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand();
+        }
+
+        /// <summary>
+        /// 根据条件表达式更新实体(为null字段不更新,注意model有默认值的情况),返回影响条数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="expressionWhere">条件表达式</param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int UpdateModelsIgnoreNull<T>(T t, Expression<Func<T, bool>> expressionWhere, SqlSugarClient db) where T : class, new()
+        {
+            return db.Updateable(t).IgnoreColumns(ignoreAllNullColumns: true).Where(expressionWhere).ExecuteCommand();
+        }
+
+        /// <summary>
+        /// 根据条件表达式更新实体(指定要更新的列),返回影响条数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="columns"></param>
+        /// <param name="expressionWhere">条件表达式</param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int UpdateModels<T>(T t, Expression<Func<T, object>> columns, Expression<Func<T, bool>> expressionWhere, SqlSugarClient db) where T : class, new()
+        {
+            return db.Updateable(t).UpdateColumns(columns).Where(expressionWhere).ExecuteCommand();
+        }
+
+        /// <summary>
+        /// 动态更新
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="columns">要更新的字段</param>
+        /// <param name="expressionWhere">where条件表达式</param>
+        /// <param name="db"></param>
+        /// <returns></returns>
+        public static int Update<T>(Expression<Func<T, T>> columns, Expression<Func<T, bool>> expressionWhere, SqlSugarClient db) where T : class, new()
+        {
+            return db.Updateable<T>().SetColumns(columns).
+                // IgnoreColumns(ignoreAllNullColumns: true).// 不能加此方法,会有“CommandText 属性尚未初始化”异常
+                Where(expressionWhere).ExecuteCommand();
+            /* 调用示例
+             Update<Entity.SysAdmin>(p => new Entity.SysAdmin { photo = photo, Password = newPwd }
+                ,p => p.ID == sm.id && p.Password == oldPwd ,db) > 0;
+             */
+        }
+
+        /// <summary>
+        /// 根据主键更新实体,返回影响条数(实体字段要有主键特性)并删除缓存,返回影响条数(实体字段要有主键特性)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int UpdateModelByKey_RemoveDataCache<T>(T t, SqlSugarClient db) where T : class, new()
+        {
+            // 字段null,不进行更新
+            return db.Updateable(t).IgnoreColumns(ignoreAllNullColumns: true).RemoveDataCache().ExecuteCommand();
+        }
+
+        /// <summary>
+        /// 根据条件表达式更新实体(指定要更新的列)并删除缓存,返回影响条数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="t"></param>
+        /// <param name="columns"></param>
+        /// <param name="expressionWhere">条件表达式</param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int UpdateModels_RemoveDataCache<T>(T t, Expression<Func<T, object>> columns, Expression<Func<T, bool>> expressionWhere, SqlSugarClient db) where T : class, new()
+        {
+            return db.Updateable(t).UpdateColumns(columns).Where(expressionWhere).RemoveDataCache().ExecuteCommand();
+        }
+
+        /// <summary>
+        /// 动态更新,并删除缓存
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="columns">要更新的字段</param>
+        /// <param name="expressionWhere">where条件表达式</param>
+        /// <param name="db"></param>
+        /// <returns></returns>
+        public static int Update_RemoveDataCache<T>(Expression<Func<T, T>> columns, Expression<Func<T, bool>> expressionWhere, SqlSugarClient db) where T : class, new()
+        {
+            return db.Updateable<T>().SetColumns(columns).
+                // IgnoreColumns(ignoreAllNullColumns: true).// 不能加此方法,会有“CommandText 属性尚未初始化”异常
+                Where(expressionWhere).RemoveDataCache().ExecuteCommand();
+            /* 调用示例
+             Update<Entity.SysAdmin>(p => new Entity.SysAdmin { photo = photo, Password = newPwd }
+                ,p => p.ID == sm.id && p.Password == oldPwd ,db) > 0;
+             */
+        }
+
+        /// <summary>
+        /// 删除ids集合条件的字符串(高效率写法,注意防止注入攻击)
+        /// </summary>
+        /// <param name="ids">ids为字符串 "1,2,3"或"1" 形式</param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <param name="key">主键字段</param>
+        /// <returns></returns>
+        public static bool DeleteByWhereSql_ids<T>(string ids, SqlSugarClient db, string key = "id") where T : class, new()
+        {
+            return db.Deleteable<T>().Where(string.Format(" {0} IN ({1})", key, ids)).ExecuteCommand() > 0;
+        }
+
+        /// <summary>
+        /// 根据条件表达式删除,返回影响条数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="expression">表达式</param>
+        /// <param name="db">(事务的db对象)</param>
+        /// <returns></returns>
+        public static int DeleteModels<T>(Expression<Func<T, bool>> expression, SqlSugarClient db) where T : class, new()
+        {
+            return db.Deleteable<T>().Where(expression).ExecuteCommand();
+        }
+    }
+}

+ 13 - 0
SKMC.API/Common/Data/IDataFilter.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Data
+{
+    public interface IDataFilter<T>
+    {
+        T Filter(T value, T param1, T param2);
+    }
+}

+ 21 - 0
SKMC.API/Common/Data/ITrendAnalyzer.cs

@@ -0,0 +1,21 @@
+using System;
+using SKMC.Api.Common.Types;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Data
+{
+    /// <summary>
+    /// 数值趋势分析器接口
+    /// </summary>
+    public interface ITrendAnalyzer
+    {
+        void SetParam(Dictionary<string, object> param);
+
+        void SetSource(BoundedQueue<float> boundedQueue);
+
+        bool CheckTrend(out string desc);
+    }
+}

+ 235 - 0
SKMC.API/Common/Datetime/MicroTimer.cs

@@ -0,0 +1,235 @@
+using System;
+
+namespace SKMC.Api.Common.Datetime
+{
+    /// <summary>
+    /// MicroStopwatch class
+    /// </summary>
+    public class MicroStopwatch : System.Diagnostics.Stopwatch
+    {
+        readonly double _microSecPerTick =
+            1000000D / System.Diagnostics.Stopwatch.Frequency;
+
+        public MicroStopwatch()
+        {
+            if (!System.Diagnostics.Stopwatch.IsHighResolution)
+            {
+                throw new Exception("On this system the high-resolution " +
+                                    "performance counter is not available");
+            }
+        }
+
+        public long ElapsedMicroseconds
+        {
+            get
+            {
+                return (long)(ElapsedTicks * _microSecPerTick);
+            }
+        }
+    }
+
+    /// <summary>
+    /// MicroTimer class
+    /// </summary>
+    public class MicroTimer
+    {
+        public delegate void MicroTimerElapsedEventHandler(
+                             object sender,
+                             MicroTimerEventArgs timerEventArgs);
+        public event MicroTimerElapsedEventHandler MicroTimerElapsed;
+
+        System.Threading.Thread _threadTimer = null;
+        long _ignoreEventIfLateBy = long.MaxValue;
+        long _timerIntervalInMicroSec = 0;
+        bool _stopTimer = true;
+
+        public MicroTimer()
+        {
+
+        }
+
+        public MicroTimer(long timerIntervalInMicroseconds)
+        {
+            Interval = timerIntervalInMicroseconds;
+        }
+
+        public long Interval
+        {
+            get
+            {
+                return System.Threading.Interlocked.Read(
+                    ref _timerIntervalInMicroSec);
+            }
+            set
+            {
+                System.Threading.Interlocked.Exchange(
+                    ref _timerIntervalInMicroSec, value);
+            }
+        }
+
+        public long IgnoreEventIfLateBy
+        {
+            get
+            {
+                return System.Threading.Interlocked.Read(
+                    ref _ignoreEventIfLateBy);
+            }
+            set
+            {
+                System.Threading.Interlocked.Exchange(
+                    ref _ignoreEventIfLateBy, value <= 0 ? long.MaxValue : value);
+            }
+        }
+
+        public bool Enabled
+        {
+            set
+            {
+                if (value)
+                {
+                    Start();
+                }
+                else
+                {
+                    Stop();
+                }
+            }
+            get
+            {
+                return (_threadTimer != null && _threadTimer.IsAlive);
+            }
+        }
+
+        public void Start()
+        {
+            if (Enabled || Interval <= 0)
+            {
+                return;
+            }
+
+            _stopTimer = false;
+
+            System.Threading.ThreadStart threadStart = delegate()
+            {
+                NotificationTimer(ref _timerIntervalInMicroSec,
+                                  ref _ignoreEventIfLateBy,
+                                  ref _stopTimer);
+            };
+
+            _threadTimer = new System.Threading.Thread(threadStart);
+            _threadTimer.Priority = System.Threading.ThreadPriority.Highest;
+            _threadTimer.Start();
+        }
+
+        public void Stop()
+        {
+            _stopTimer = true;
+        }
+
+        public void StopAndWait()
+        {
+            StopAndWait(System.Threading.Timeout.Infinite);
+        }
+
+        public bool StopAndWait(int timeoutInMilliSec)
+        {
+            _stopTimer = true;
+
+            if (!Enabled || _threadTimer.ManagedThreadId ==
+                System.Threading.Thread.CurrentThread.ManagedThreadId)
+            {
+                return true;
+            }
+
+            return _threadTimer.Join(timeoutInMilliSec);
+        }
+
+        public void Abort()
+        {
+            _stopTimer = true;
+
+            if (Enabled)
+            {
+                _threadTimer.Abort();
+            }
+        }
+
+        void NotificationTimer(ref long timerIntervalInMicroSec,
+                               ref long ignoreEventIfLateBy,
+                               ref bool stopTimer)
+        {
+            int  timerCount = 0;
+            long nextNotification = 0;
+
+            MicroStopwatch microStopwatch = new MicroStopwatch();
+            microStopwatch.Start();
+
+            while (!stopTimer)
+            {
+                long callbackFunctionExecutionTime =
+                    microStopwatch.ElapsedMicroseconds - nextNotification;
+
+                long timerIntervalInMicroSecCurrent =
+                    System.Threading.Interlocked.Read(ref timerIntervalInMicroSec);
+                long ignoreEventIfLateByCurrent =
+                    System.Threading.Interlocked.Read(ref ignoreEventIfLateBy);
+
+                nextNotification += timerIntervalInMicroSecCurrent;
+                timerCount++;
+                long elapsedMicroseconds = 0;
+
+                while ( (elapsedMicroseconds = microStopwatch.ElapsedMicroseconds)
+                        < nextNotification)
+                {
+                    System.Threading.Thread.SpinWait(10);
+                }
+
+                long timerLateBy = elapsedMicroseconds - nextNotification;
+
+                if (timerLateBy >= ignoreEventIfLateByCurrent)
+                {
+                    continue;
+                }
+
+                MicroTimerEventArgs microTimerEventArgs =
+                     new MicroTimerEventArgs(timerCount,
+                                             elapsedMicroseconds,
+                                             timerLateBy,
+                                             callbackFunctionExecutionTime);
+                MicroTimerElapsed(this, microTimerEventArgs);
+            }
+
+            microStopwatch.Stop();
+        }
+    }
+
+    /// <summary>
+    /// MicroTimer Event Argument class
+    /// </summary>
+    public class MicroTimerEventArgs : EventArgs
+    {
+        // Simple counter, number times timed event (callback function) executed
+        public int  TimerCount { get; private set; }
+
+        // Time when timed event was called since timer started
+        public long ElapsedMicroseconds { get; private set; }
+
+        // How late the timer was compared to when it should have been called
+        public long TimerLateBy { get; private set; }
+
+        // Time it took to execute previous call to callback function (OnTimedEvent)
+        public long CallbackFunctionExecutionTime { get; private set; }
+
+        public MicroTimerEventArgs(int  timerCount,
+                                   long elapsedMicroseconds,
+                                   long timerLateBy,
+                                   long callbackFunctionExecutionTime)
+        {
+            TimerCount = timerCount;
+            ElapsedMicroseconds = elapsedMicroseconds;
+            TimerLateBy = timerLateBy;
+            CallbackFunctionExecutionTime = callbackFunctionExecutionTime;
+        }
+
+    }
+}

+ 71 - 0
SKMC.API/Common/Exceptions/ExceptionBase.cs

@@ -0,0 +1,71 @@
+using System;
+
+namespace SKMC.Api.Common.Exceptions
+{
+    /// <summary>
+    /// 基础异常模型
+    /// </summary>
+    public class ExceptionBase : Exception
+    {
+        /// <summary>
+        /// 内部异常类型
+        /// </summary>
+        public int Type { get; set; }
+
+        /// <summary>
+        /// 异常码
+        /// </summary>
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 异常级别
+        /// 0: 不影响运行的告警
+        /// 1: 影响不大, 可暂时忽略
+        /// 2: 影响较大, 需设备告警并解决完毕后才可继续
+        /// 4: 影响非常大, 可能伤人或伤设备, 设备告警时系统会自动退出运行
+        /// </summary>
+        public short Level { get; set; }
+
+        /// <summary>
+        /// 问题点
+        /// </summary>
+        public string Detail { get; set; }
+
+        /// <summary>
+        /// 异常说明及建议
+        /// </summary>
+        public string Tips { get; set; }
+
+        /// <summary>
+        /// 异常时的站点Id
+        /// </summary>
+        public int StationId { get; set; }
+
+        /// <summary>
+        /// 发生时间
+        /// </summary>
+        public DateTime EncounterTime { get; set; }
+
+        /// <summary>
+        /// 重试动作, 完成重试后需要再次验证
+        /// </summary>
+        public Func<bool> RetryAction { get; set; }
+
+        /// <summary>
+        /// 忽略动作
+        /// </summary>
+        public Action IgnoreAction { get; set; }
+
+        /// <summary>
+        /// 放弃/退出动作
+        /// </summary>
+        public Action AbortAction { get; set; }
+
+        
+        public override string ToString()
+        {
+            return $"Code: [{Code}], Type: [{Type}], Level: {Level}, Detail: {Detail}, Message: {Message}, Source: {Source}";
+        }
+
+    }
+}

+ 155 - 0
SKMC.API/Common/Exceptions/ExceptionConfig.cs

@@ -0,0 +1,155 @@
+using Prism.Mvvm;
+using System;
+
+namespace SKMC.Api.Common.Exceptions
+{
+    /// <summary>
+    /// 异常处理配置模型
+    /// </summary>
+    public class ExceptionConfig : BindableBase
+    {
+        public ExceptionConfig() { }
+
+        public ExceptionConfig(ExceptionConfig exceptionConfiger)
+        {
+            if (exceptionConfiger != null)
+            {
+                Id = exceptionConfiger.Id;
+                Code = exceptionConfiger.Code;
+                Type = exceptionConfiger.Type;
+                Level = exceptionConfiger.Level;
+                Group = exceptionConfiger.Group;
+                Name = exceptionConfiger.Name;
+                Note = exceptionConfiger.Note;
+                Tips = exceptionConfiger.Tips;
+                CanRetry = exceptionConfiger.CanRetry;
+                CanIgnore = exceptionConfiger.CanIgnore;
+                CanAbort = exceptionConfiger.CanAbort;
+                RetryMsg = exceptionConfiger.RetryMsg;
+                RetryMsgSuccess = exceptionConfiger.RetryMsgSuccess;
+                RetryMsgFailed = exceptionConfiger.RetryMsgFailed;
+            }
+        }
+
+        protected long _id;
+
+        public long Id
+        {
+            get { return _id; }
+            set { _id = value; }
+        }
+
+        protected string _code;
+
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; }
+        }
+
+        protected int _type;
+
+        public int Type
+        {
+            get { return _type; }
+            set { _type = value; }
+        }
+
+        protected string _group;
+
+        public string Group
+        {
+            get { return _group; }
+            set { _group = value; }
+        }
+
+        protected string _name;
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; }
+        }
+
+        protected string _note;
+
+        public string Note
+        {
+            get { return _note; }
+            set { _note = value; }
+        }
+
+        protected string _tips;
+
+        public string Tips
+        {
+            get { return _tips; }
+            set { _tips = value; }
+        }
+
+        protected short _level;
+
+        public short Level
+        {
+            get { return _level; }
+            set { _level = value; }
+        }
+
+        protected byte _canRetry;
+
+        public byte CanRetry
+        {
+            get { return _canRetry; }
+            set { _canRetry = value; }
+        }
+
+        protected byte _canIgnore;
+
+        public byte CanIgnore
+        {
+            get { return _canIgnore; }
+            set { _canIgnore = value; }
+        }
+
+        protected byte _canAbort;
+
+        public byte CanAbort
+        {
+            get { return _canAbort; }
+            set { _canAbort = value; }
+        }
+
+        protected string _operation;
+
+        public string Operation
+        {
+            get { return _operation; }
+            set { _operation = value; RaisePropertyChanged(); }
+        }
+
+        protected string _retryMsg;
+
+        public string RetryMsg
+        {
+            get { return _retryMsg; }
+            set { _retryMsg = value; RaisePropertyChanged(); }
+        }
+
+        protected string _retryMsgSuccess;
+
+        public string RetryMsgSuccess
+        {
+            get { return _retryMsgSuccess; }
+            set { _retryMsgSuccess = value; RaisePropertyChanged(); }
+        }
+
+        protected string _retryMsgFailed;
+
+        public string RetryMsgFailed
+        {
+            get { return _retryMsgFailed; }
+            set { _retryMsgFailed = value; RaisePropertyChanged(); }
+        }
+
+    }
+}

+ 66 - 0
SKMC.API/Common/Exceptions/ExceptionShow.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Exceptions
+{
+    /// <summary>
+    /// 基于异常配置的异常展示模型
+    /// </summary>
+    public class ExceptionShow : ExceptionConfig, ICloneable
+    {
+        public ExceptionShow(ExceptionConfig exceptionConfig) : base(exceptionConfig) { }
+
+        private string _uid;
+
+        public string Uid
+        {
+            get { return _uid; }
+            set { _uid = value; }
+        }
+
+
+        private DateTime _encounterTime;
+        /// <summary>
+        /// 异常发生时间
+        /// </summary>
+        public DateTime EncounterTime
+        {
+            get { return _encounterTime; }
+            set { _encounterTime = value; RaisePropertyChanged(); }
+        }
+
+        private string _stationName;
+        /// <summary>
+        /// 异常发生工位
+        /// </summary>
+        public string StationName
+        {
+            get { return _stationName; }
+            set { _stationName = value; }
+        }
+
+        private string _detail;
+        /// <summary>
+        /// 异常详情
+        /// </summary>
+        public string Detail
+        {
+            get { return _detail; }
+            set { _detail = value; RaisePropertyChanged(); }
+        }
+
+        public Func<bool> RetryAction { get; set; }
+
+        public Action IgnoreAction { get; set; }
+
+        public Action AbortAction { get; set; }
+
+        public object Clone()
+        {
+            return this.MemberwiseClone();
+        }
+    }
+}

+ 60 - 0
SKMC.API/Common/Expression/Predicator.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace SKMC.Api.Common
+{
+    public static class Predicator
+    {
+        public static Expression<Func<T, bool>> GetTrue<T>() { return f => true; }
+        public static Expression<Func<T, bool>> GetFalse<T>() { return f => false; }
+
+        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
+        {
+            return first.AndAlso<T>(second, Expression.AndAlso);
+        }
+
+        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
+        {
+            return first.AndAlso<T>(second, Expression.OrElse);
+        }
+
+        private static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2, Func<Expression, Expression, BinaryExpression> func)
+        {
+            var parameter = Expression.Parameter(typeof(T));
+
+            var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
+            var left = leftVisitor.Visit(expr1.Body);
+
+            var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
+            var right = rightVisitor.Visit(expr2.Body);
+
+            return Expression.Lambda<Func<T, bool>>(
+                func(left, right), parameter);
+        }
+
+        public static Expression<Func<T, bool>> AggregateAnd<T>(Expression<Func<T, bool>>[] input)
+        {
+            return input.Aggregate((l, r) => l.And(r));
+        }
+
+        private class ReplaceExpressionVisitor : ExpressionVisitor
+        {
+            private readonly Expression _oldValue;
+            private readonly Expression _newValue;
+
+            public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
+            {
+                _oldValue = oldValue;
+                _newValue = newValue;
+            }
+
+            public override Expression Visit(Expression node)
+            {
+                if (node == _oldValue)
+                    return _newValue;
+                return base.Visit(node);
+            }
+        }
+    }
+}

+ 42 - 0
SKMC.API/Common/File/AppConfig.cs

@@ -0,0 +1,42 @@
+
+namespace SKMC.Api.Common.File
+{
+    /// <summary>
+    /// 应用配置文件类
+    /// </summary>
+    public class AppConfig
+    {
+
+        private static AppConfig instance;
+
+        private readonly IniFiles iniFiles;
+
+        private AppConfig(string fileName)
+        {
+            string name = fileName ?? "AppSettings.ini";
+            iniFiles = new IniFiles(System.Environment.CurrentDirectory + $"\\{name}");
+        }
+
+        public static AppConfig Instance(string fileName = null)
+        {
+            if (instance == null) instance = new AppConfig(fileName);
+            return instance;
+        }
+
+        /// <summary>
+        /// 获取一个string类型的配置项
+        /// </summary>
+        /// <param name="section"></param>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public string ReadStringValue(string section, string key) => iniFiles.IniReadStringValue(section, key);
+
+        /// <summary>
+        /// 获取一个bool类型的配置项
+        /// </summary>
+        /// <param name="section"></param>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public bool ReadBoolValue(string section, string key) => iniFiles.IniReadBoolValue(section, key);
+    }
+}

+ 114 - 0
SKMC.API/Common/File/IniFiles.cs

@@ -0,0 +1,114 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace SKMC.Api.Common.File
+{
+    /// <summary>
+    /// Ini文件读写工具类
+    /// </summary>
+    public class IniFiles
+    {
+        public string iniPath;
+
+        [DllImport("kernel32")]
+        private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
+        [DllImport("kernel32")]
+        public static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="INIPath">路径</param>
+        public IniFiles(string INIPath)
+        {
+            iniPath = INIPath;
+
+            if (!System.IO.File.Exists(iniPath))
+            {
+                try
+                {
+                    System.IO.File.Create(iniPath);
+                }
+                catch (Exception)
+                { }
+            }
+        }
+
+        /// <summary>
+        /// 向ini文件中写入值
+        /// </summary>
+        /// <param name="Section">表头</param>
+        /// <param name="Key">键</param>
+        /// <param name="Value">值</param>
+        public void IniWriteValue(string Section, string Key, string Value)
+        {
+            WritePrivateProfileString(Section, Key, Value, this.iniPath);
+        }
+
+        /// <summary>
+        /// 从ini中得到int类型的值
+        /// </summary>
+        /// <param name="Section">表头</param>
+        /// <param name="Key">键</param>
+        /// <returns></returns>
+        public int IniReadIntValue(string Section, string Key)
+        {
+            return Convert.ToInt32(IniReadStringValue(Section, Key));
+        }
+
+        /// <summary>
+        /// 从ini中得到double类型的值
+        /// </summary>
+        /// <param name="Section">表头</param>
+        /// <param name="Key">键</param>
+        /// <returns></returns>
+        public double IniReadDoubleValue(string Section, string Key)
+        {
+            return Convert.ToDouble(IniReadStringValue(Section, Key));
+        }
+
+
+        public bool IniReadBoolValue(string Section, string Key)
+        {
+            return Convert.ToBoolean(IniReadStringValue(Section, Key));
+        }
+
+        /// <summary>
+        /// 从ini中得到string类型的值
+        /// </summary>
+        /// <param name="Section">表头</param>
+        /// <param name="Key">键</param>
+        /// <returns></returns>
+        public string IniReadStringValue(string Section, string Key)
+        {
+            StringBuilder temp = new StringBuilder(1024);
+            GetPrivateProfileString(Section, Key, "", temp, 1024, this.iniPath);
+            return temp.ToString();
+        }
+
+        /// <summary>
+        /// 判断文件是否存在
+        /// </summary>
+        /// <returns></returns>
+        public bool ExistINIFile()
+        {
+            return System.IO.File.Exists(iniPath);
+        }
+
+        /// <summary>
+        /// 判断键值是否相同
+        /// </summary>
+        /// <param name="Section">表头</param>
+        /// <param name="Key">键</param>
+        /// <param name="Value">值</param>
+        /// <param name="specBeefore">设定值</param>
+        public void IniWriteValue(string Section, string Key, string Value, string specBeefore)
+        {
+            if (Value != specBeefore)
+            {
+                WritePrivateProfileString(Section, Key, Value, this.iniPath);
+            }
+        }
+    }
+}

+ 90 - 0
SKMC.API/Common/File/XmlSerializerTool.cs

@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Xml.Serialization;
+
+namespace SKMC.Api.Common.File
+{
+    /// <summary>
+    /// XML序列化与反序列化工具类
+    /// </summary>
+    public class XmlSerializerTool
+    {
+        private static readonly object locker = new object();
+
+        public static T Read<T>(string content) where T : class
+        {
+            if (String.IsNullOrEmpty(content)) return default;
+            StringReader stringReader = new StringReader(content);
+            try
+            {
+                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
+                return xmlSerializer.Deserialize(stringReader) as T;
+            }
+            finally
+            {
+                if (stringReader != null) stringReader.Close();
+            }
+        }
+
+        public static T ReadFrom<T>(string path) where T : class
+        {
+            T local = default;
+            if (!System.IO.File.Exists(path))
+            {
+                return local;
+            }
+            FileStream fileStream = null;
+            try
+            {
+                fileStream = new FileStream(path, FileMode.Open);
+                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
+                return xmlSerializer.Deserialize(fileStream) as T;
+            }
+            finally
+            {
+                if (fileStream != null) fileStream.Close();
+            }
+        }
+
+        public static string Save<T>(T obj) where T : class
+        {
+            if (obj == null) return null;
+            MemoryStream memoryStream = null;
+            try
+            {
+                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
+                ns.Add("", "");
+                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
+                memoryStream = new MemoryStream();
+                xmlSerializer.Serialize(memoryStream, obj, ns);
+                return UTF8Encoding.UTF8.GetString(memoryStream.ToArray());
+            }
+            finally
+            {
+                if (memoryStream != null) memoryStream.Close();
+            }
+        }
+
+        public static void SaveTo<T>(string path, T obj)
+        {
+            object locker = XmlSerializerTool.locker;
+            lock (locker)
+            {
+                FileStream fileStream = null;
+                try
+                {
+                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
+                    ns.Add("", "");
+                    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
+                    fileStream = new FileStream(path, FileMode.Create);
+                    xmlSerializer.Serialize(fileStream, obj, ns);
+                }
+                finally
+                {
+                    if (fileStream != null) fileStream.Close();
+                }
+            }
+        }
+    }
+}

+ 30 - 0
SKMC.API/Common/Logger/ILogger.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    /// <summary>
+    /// 通用的日志接口
+    /// </summary>
+    public interface ILogger
+    {
+        /// <summary>
+        /// 调试跟踪(预留)
+        /// </summary>
+        /// <param name="message"></param>
+        void Trace(string message);
+
+        void Debug(string message);
+
+        void Info(string message);
+
+        void Warn(string message);
+
+        void Error(string message, Exception ex = null);
+
+        void Fatal(string message, Exception ex = null);
+    }
+}

+ 55 - 0
SKMC.API/Common/Logger/Log4netLogger.cs

@@ -0,0 +1,55 @@
+using log4net;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    public class Log4netLogger : ILogger
+    {
+
+        private readonly ILog log;
+
+        public Log4netLogger(Type type)
+        {
+            log = LogManager.GetLogger(type);
+        }
+
+        public Log4netLogger(string name)
+        {
+            log = LogManager.GetLogger(name);
+        }
+
+        public void Debug(string message)
+        {
+            log.Debug(message);
+        }
+
+        public void Info(string message)
+        {
+            log.Debug(message);
+        }
+
+        public void Trace(string message)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Warn(string message)
+        {
+            log.Warn(message);
+        }
+
+        public void Error(string message, Exception ex = null)
+        {
+            log.Error(message, ex);
+        }
+
+        public void Fatal(string message, Exception ex = null)
+        {
+            log.Fatal(message, ex);
+        }
+    }
+}

+ 29 - 0
SKMC.API/Common/Logger/LogDataService.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace SKMC.Api.Common
+{
+    public class LogDataService
+    {
+        private static LogDataService instance;
+
+        private readonly object lockObject = new object();
+        public ObservableCollection<LoggingEventModel> LogEventModels { get; set; } = new ObservableCollection<LoggingEventModel>();
+
+        public static LogDataService Instance()
+        {
+            if (instance == null) instance = new LogDataService();
+            return instance;
+        }
+
+        private LogDataService()
+        {
+            BindingOperations.EnableCollectionSynchronization(LogEventModels, lockObject);
+        }
+    }
+}

+ 50 - 0
SKMC.API/Common/Logger/LogFactory.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    public class LogFactory
+    {
+        /// <summary>
+        /// "Log4Net" 或 "NLog"
+        /// </summary>
+        private static readonly string LoggerType = "Log4Net";
+
+        public static ILogger Get(Type type)
+        {
+            if (LoggerType == "Log4Net")
+            {
+                return new Log4netLogger(type);
+            }
+            else if (LoggerType == "NLog")
+            {
+                return new NLogLogger(type.FullName);
+            }
+            else
+            {
+                throw new NotSupportedException($"Logger type {LoggerType} is not supported.");
+            }
+        }
+
+        public static ILogger Get([CallerFilePath] string callerFilePath = "")
+        {
+            var callerType = System.IO.Path.GetFileNameWithoutExtension(callerFilePath);
+            if (LoggerType == "Log4Net")
+            {
+                return new Log4netLogger(callerType);
+            }
+            else if (LoggerType == "NLog")
+            {
+                return new NLogLogger(callerType);
+            }
+            else
+            {
+                throw new NotSupportedException($"Logger type {LoggerType} is not supported.");
+            }
+        }
+    }
+}

+ 35 - 0
SKMC.API/Common/Logger/LoggingEventAppender.cs

@@ -0,0 +1,35 @@
+using log4net.Appender;
+using log4net.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    /// <summary>
+    /// log4net 扩展
+    /// </summary>
+    public class LoggingEventAppender : AppenderSkeleton
+    {
+        static readonly int LOGVIEW_MAXLINE = 1000;
+
+        private readonly LogDataService logDataService = LogDataService.Instance();
+
+        protected override void Append(LoggingEvent loggingEvent)
+        {
+            //logDataService.LogEventModels.Insert(0, new LoggingEventModel(loggingEvent));
+            //if (logDataService.LogEventModels.Count == LOGVIEW_MAXLINE)
+            //{
+            //    logDataService.LogEventModels.RemoveAt(LOGVIEW_MAXLINE - 1);
+            //}
+
+            logDataService.LogEventModels.Add(new LoggingEventModel(loggingEvent));
+            if (logDataService.LogEventModels.Count == LOGVIEW_MAXLINE)
+            {
+                logDataService.LogEventModels.RemoveAt(0);
+            }
+        }
+    }
+}

+ 40 - 0
SKMC.API/Common/Logger/LoggingEventModel.cs

@@ -0,0 +1,40 @@
+using log4net.Core;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    public class LoggingEventModel
+    {
+        public string Logger { get; set; }
+        public string Timestamp { get; set; }
+        public string Level { get; set; }
+        public string Thread { get; set; }
+        public string Message { get; set; }
+        public string Exception { get; set; }
+
+        public LoggingEventModel(LoggingEvent loggingEvent)
+        {
+            Logger = loggingEvent.LoggerName;
+            Timestamp = loggingEvent.TimeStamp.ToString("yyyy-MM-dd HH:mm:ss,fff");
+            Level = loggingEvent.Level.Name;
+            Thread = loggingEvent.ThreadName;
+            Message = loggingEvent.RenderedMessage;
+            Exception = loggingEvent.GetExceptionString();
+        }
+
+        public LoggingEventModel(LogEventInfo loggingEvent)
+        {
+            Logger = loggingEvent.LoggerName;
+            Timestamp = loggingEvent.TimeStamp.ToString("yyyy-MM-dd HH:mm:ss,fff");
+            Level = loggingEvent.Level.Name;
+            Thread = Convert.ToString(System.Threading.Thread.CurrentThread.ManagedThreadId);
+            Message = loggingEvent.Message;
+            Exception = loggingEvent.Exception.Message;
+        }
+    }
+}

+ 30 - 0
SKMC.API/Common/Logger/LoggingEventTarget.cs

@@ -0,0 +1,30 @@
+using NLog;
+using NLog.Targets;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    /// <summary>
+    /// NLog 扩展
+    /// </summary>
+    public class LoggingEventTarget : TargetWithLayout
+    {
+        static readonly int LOGVIEW_MAXLINE = 1000;
+
+        private readonly LogDataService logDataService = LogDataService.Instance();
+
+        protected override void Write(LogEventInfo loggingEvent)
+        {
+            logDataService.LogEventModels.Insert(0, new LoggingEventModel(loggingEvent));
+
+            if (logDataService.LogEventModels.Count == LOGVIEW_MAXLINE)
+            {
+                logDataService.LogEventModels.RemoveAt(LOGVIEW_MAXLINE - 1);
+            }
+        }
+    }
+}

+ 49 - 0
SKMC.API/Common/Logger/NLogLogger.cs

@@ -0,0 +1,49 @@
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common
+{
+    public class NLogLogger : ILogger
+    {
+
+        private readonly Logger log;
+
+        public NLogLogger(string name)
+        {
+            log = LogManager.GetLogger(name);
+        }
+
+        public void Debug(string message)
+        {
+            log.Debug(message);
+        }
+        public void Info(string message)
+        {
+            log.Info(message);
+        }
+
+        public void Trace(string message)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Warn(string message)
+        {
+            log.Warn(message);
+        }
+
+        public void Error(string message, Exception ex = null)
+        {
+            log.Error(message, ex);
+        }
+
+        public void Fatal(string message, Exception ex = null)
+        {
+            log.Fatal(message, ex);
+        }
+    }
+}

+ 37 - 0
SKMC.API/Common/Monitor/BaseMonitor.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Monitor
+{
+    public class BaseMonitor
+    {
+        public Timer timer;
+
+        public int CT { get; set; } = 100;
+
+        public bool IsRunning { get; set; }
+
+        public void Start()
+        {
+            if (!IsRunning)
+            {
+                timer.Change(0, CT);
+                IsRunning = true;
+            }
+        }
+
+        public void Close()
+        {
+            IsRunning = false;
+        }
+
+        public void Destroy()
+        {
+            timer.Dispose();
+        }
+    }
+}

+ 186 - 0
SKMC.API/Common/Monitor/StateLatchManager.cs

@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Timers;
+
+namespace SKMC.Api.Common.Monitor
+{
+    /// <summary>
+    /// 状态锁存管理器, 用于低频的状态判断并锁定
+    /// 例如总线连接状态、设备物料状态、是否长时间无人操作等
+    /// </summary>
+    public class StateLatchManager : IDisposable
+    {
+        private readonly Dictionary<string, LatchTask> _tasks = new Dictionary<string, LatchTask>();
+        private readonly Timer _timer;
+
+        /// <summary>
+        /// 监测并锁定的状态
+        /// </summary>
+        private static readonly int LATCH_VAL = -1;
+
+        public StateLatchManager(int interval = 5000)
+        {
+            _timer = new Timer(interval);
+            _timer.Elapsed += (s, e) => RunAllTasks();
+            // 没有限制重入
+            _timer.AutoReset = true;
+        }
+
+        /// <summary>
+        /// 添加任务,支持返回 bool 或可转换为 int 的数值类型
+        /// </summary>
+        public void AddTask<T>(string key, Func<T> task, Action callback = null, LatchMode mode = LatchMode.OpenMode)
+        {
+            _tasks[key] = new LatchTask
+            {
+                Task = task,
+                Callback = callback,
+                Mode = mode
+            };
+        }
+
+        /// <summary>
+        /// 获取任务信息
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public LatchTask GetTask(string key)
+        {
+            return _tasks.ContainsKey(key) ? _tasks[key] : null;
+        }
+
+        /// <summary>
+        /// 获取累计结果
+        /// </summary>
+        public int GetResult(string key)
+        {
+            // TODO 如果获取结果的时间戳与当前时间戳差距较大则无效
+            return _tasks.ContainsKey(key) ? _tasks[key].LatchResult : 0;
+        }
+
+        /// <summary>
+        /// 重置任务结果
+        /// </summary>
+        public void ResetResult(string key)
+        {
+            if (_tasks.ContainsKey(key))
+            {
+                _tasks[key].LatchResult = 0;
+                _tasks[key].ErrorInfo = null;
+                _tasks[key].Initialized = false;
+            }
+        }
+
+        public void Start() => _timer.Start();
+        public void Stop() => _timer.Stop();
+
+        private void RunAllTasks()
+        {
+            foreach (var task in _tasks)
+            {
+                if (task.Value.LatchResult != LATCH_VAL && task.Value.ErrorInfo == null)
+                {
+                    try
+                    {
+                        var rawResult = task.Value.Task.DynamicInvoke();
+                        int result = ConvertToInt(rawResult);
+
+                        if (task.Value.Mode == LatchMode.CloseMode)
+                        {
+                            if (task.Value.Initialized)
+                            {
+                                task.Value.LatchResult &= result;
+                                task.Value.LatchResult = task.Value.LatchResult == 0 ? -1 : 1;
+                            }
+                            else
+                            {
+                                task.Value.LatchResult = result == 0 ? -1 : 1;
+                                task.Value.Initialized = true;
+                            }
+                        }
+                        else if (task.Value.Mode == LatchMode.OpenMode)
+                        {
+                            if (task.Value.Initialized)
+                            {
+                                task.Value.LatchResult &= result;
+                                task.Value.LatchResult = task.Value.LatchResult == 0 ? 1 : -1;
+                            }
+                            else
+                            {
+                                task.Value.LatchResult = result == 0 ? 1 : -1;
+                                task.Value.Initialized = true;
+                            }
+                        }
+                        if (task.Value.LatchResult == LATCH_VAL)
+                        {
+                            task.Value.LatchTime = DateTime.Now;
+                            task.Value.Callback?.Invoke();
+                        }
+                    }
+                    catch(Exception e)
+                    {
+                        task.Value.ErrorInfo = $"ErrorInfo: {e.Message}, Datetime: {DateTime.Now}";
+                    }
+                }
+            }
+        }
+
+        private int ConvertToInt(object value)
+        {
+            if (value is bool b)
+                return b ? 1 : 0;
+            return Convert.ToInt32(value);
+        }
+
+        public void Dispose() => _timer?.Dispose();
+    }
+
+    public class LatchTask
+    {
+        /// <summary>
+        /// 监测任务
+        /// </summary>
+        public Delegate Task { get; set; }
+        /// <summary>
+        /// 是否已初始化
+        /// </summary>
+        public bool Initialized { get; set; }
+        /// <summary>
+        /// 当前结果
+        /// </summary>
+        public int LatchResult { get; set; }
+        /// <summary>
+        /// 锁存模式
+        /// </summary>
+        public LatchMode Mode { get; set; }
+        /// <summary>
+        /// 触发时间
+        /// </summary>
+        public DateTime LatchTime { get; set; }
+        /// <summary>
+        /// 异常信息
+        /// </summary>
+        public string ErrorInfo { get; set; }
+        /// <summary>
+        /// 触发后回调动作
+        /// </summary>
+        public Action Callback { get; set; }
+    }
+
+    public enum LatchMode
+    {
+        /// <summary>
+        /// 适用常闭模式:11101/11001返回-1,11111返回1
+        /// </summary>
+        CloseMode,
+
+        /// <summary>
+        /// 适用常开模式:00010/00110返回-1,00000返回1
+        /// </summary>
+        OpenMode
+    }
+
+}

+ 89 - 0
SKMC.API/Common/ObjectFactory.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+
+namespace SKMC.Api.Common
+{
+    /// <summary>
+    /// <p>接口工厂</p>
+    /// <br>类似IOC容器, 可将接口与实例注册绑定, 在需要调用接口时获取实例</br>
+    /// <br>Resolve获取单例, Create创建新实例</br>
+    /// </summary>
+    public class ObjectFactory
+    {
+        private static readonly ILogger log = LogFactory.Get();
+
+        private static readonly Dictionary<Type, Dictionary<string, object>> container = new Dictionary<Type, Dictionary<string, object>>();
+
+        /// <summary>
+        /// 注册接口实例
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="obj"></param>
+        /// <param name="name"></param>
+        public static void Register<T>(object obj, string name = "default")
+        {
+            Type type = typeof(T);
+            if (!container.ContainsKey(type))
+            {
+                container[type] = new Dictionary<string, object>();
+            }
+            //else
+            //{
+            //    throw new SystemException($"Register Failed, Cannot Register SameType: {type.Name}");
+            //}
+            container[type][name] = obj;
+            log.Debug($"Register {type.Name} success");
+        }
+
+        /// <summary>
+        /// 反注册接口实例, 重复注册前需要反注册
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        public static void UnRegister<T>()
+        {
+            Type type = typeof(T);
+            if (container.ContainsKey(type))
+            {
+                bool result = container.Remove(type);
+                log.Debug($"UnRegister Type: {type.Name}: {result}");
+            }
+        }
+
+        /// <summary>
+        /// 解析接口实例(单例)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="name"></param>
+        /// <returns></returns>
+        public static T Resolve<T>(string name = "default")
+        {
+            Type type = typeof(T);
+            if (container.ContainsKey(type) && container[type].ContainsKey(name))
+            {
+                object obj = (T)container[type][name];
+                //log.Debug($"Create {type.Name} success, obj: {obj.GetHashCode()}");
+                return (T)obj;
+            }
+            throw new SystemException($"Resolve Faild, No object for [{type}] with name [{name}] found.");
+        }
+
+        /// <summary>
+        /// 创建接口实例(多例)
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="name"></param>
+        /// <returns></returns>
+        public static T Create<T>(string name = "default")
+        {
+            Type type = typeof(T);
+            if (container.ContainsKey(type) && container[type].ContainsKey(name))
+            {
+                object objectNow = container[type][name];
+                object objectNew = Activator.CreateInstance(objectNow.GetType());
+                //log.Debug($"Create {type.Name} success, obj: {objectNew.GetHashCode()}");
+                return (T)objectNew;
+            }
+            throw new SystemException($"Create Faild, No object for [{type}] with name [{name}] found.");
+        }
+    }
+}

+ 24 - 0
SKMC.API/Common/Tasks/TaskToken.cs

@@ -0,0 +1,24 @@
+using System.Threading;
+
+namespace SKMC.Api.Common.Tasks
+{
+    public class TaskToken
+    {
+        public CancellationTokenSource TokenSource { get; set; } = new CancellationTokenSource();
+
+        public CancellationToken Token { get; set; }
+
+        public ManualResetEvent ResetEvent { get; set; } = new ManualResetEvent(true);
+
+        public TaskToken()
+        {
+            Token = TokenSource.Token;
+        }
+
+        public void SetWaitPoint()
+        {
+            ResetEvent.WaitOne();
+        }
+
+    }
+}

+ 101 - 0
SKMC.API/Common/Tasks/TaskTokener.cs

@@ -0,0 +1,101 @@
+using System.Collections.Concurrent;
+
+namespace SKMC.Api.Common.Tasks
+{
+    public class TaskTokener
+    {
+
+        private static readonly ConcurrentDictionary<int, TaskToken> taskTokens = new ConcurrentDictionary<int, TaskToken>();
+
+        public static void Add(int id)
+        {
+            if (taskTokens.ContainsKey(id))
+            {
+                taskTokens.TryRemove(id, out var taskToken);
+                taskToken?.TokenSource.Cancel();
+            };
+            taskTokens.TryAdd(id, new TaskToken());
+        }
+
+        public static TaskToken GetTaskToken(int id)
+        {
+            if (!taskTokens.ContainsKey(id))
+            {
+                Add(id);
+            }
+            return taskTokens[id];
+        }
+
+        public static void SetWaitPoint(int id)
+        {
+            TaskToken taskToken = GetTaskToken(id);
+            if (taskToken == null) return;
+            taskToken.SetWaitPoint();
+        }
+
+
+        public static bool IsCancelled(int id)
+        {
+            TaskToken taskToken = GetTaskToken(id);
+            if (taskToken == null) return true;
+            bool result = taskToken.TokenSource.IsCancellationRequested;
+            return result;
+        }
+
+        public static void Pause(int id)
+        {
+            TaskToken taskToken = GetTaskToken(id);
+            if (taskToken == null) return;
+            taskToken.ResetEvent.Reset();
+        }
+
+        public static void PauseAll()
+        {
+            foreach (int stationId in taskTokens.Keys)
+            {
+                Pause(stationId);
+            }
+        }
+
+        public static void Resume(int id)
+        {
+            TaskToken taskToken = GetTaskToken(id);
+            if (taskToken == null) return;
+            taskToken.ResetEvent.Set();
+        }
+
+        public static void ResumeAll()
+        {
+            foreach (int station in taskTokens.Keys)
+            {
+                Resume(station);
+            }
+        }
+
+        public static void Stop(int id)
+        {
+            TaskToken taskToken = GetTaskToken(id);
+            if (taskToken == null) return;
+            taskToken.TokenSource.Cancel();
+            taskToken.ResetEvent.Set();
+        }
+
+        public static void StopAll()
+        {
+            foreach (int stationId in taskTokens.Keys)
+            {
+                Stop(stationId);
+            }
+        }
+
+        public static void RestoreAll()
+        {
+            foreach (int stationId in taskTokens.Keys)
+            {
+                TaskToken taskToken = GetTaskToken(stationId);
+                if (taskToken == null) continue;
+                Add(stationId);
+            }
+        }
+    }
+}

+ 80 - 0
SKMC.API/Common/Tasks/Tasks.cs

@@ -0,0 +1,80 @@
+using SKMC.Api.Process.Control;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Tasks
+{
+    /// <summary>
+    /// 任务运行相关扩展方法
+    /// </summary>
+    public class Tasks
+    {
+        private static readonly ILogger log = LogFactory.Get();
+
+        /// <summary>
+        /// 多任务运行, 异常时弹窗告警
+        /// </summary>
+        /// <param name="taskWait">true表示执行任务后等待任务完成, false表示不等待(可以由返回的tasks自行处理)</param>
+        /// <param name="actions"></param>
+        /// <returns></returns>
+        public static List<Task> Run(bool taskWait = true, params Action[] actions)
+        {
+            var tasks = new List<Task>();
+            foreach (var item in actions)
+            {
+                var task = Task.Run(() =>
+                {
+                    try
+                    {
+                        item.Invoke();
+                    }
+                    catch (Exception e)
+                    {
+                        ProcessExceptions.WrapThrows(e);
+                    }
+                });
+                tasks.Add(task);
+            }
+            if (taskWait) Task.WaitAll(tasks.ToArray());
+            return tasks;
+        }
+
+
+        /// <summary>
+        /// 多任务运行, 异常时不弹窗只打日志
+        /// </summary>
+        /// <param name="taskWait">true表示执行任务后等待任务完成, false表示不等待(可以由返回的tasks自行处理)</param>
+        /// <param name="actions"></param>
+        /// <returns></returns>
+        public static List<Task> RunNoAlarm(bool taskWait = true, params Action[] actions)
+        {
+            var tasks = new List<Task>();
+            foreach (var item in actions)
+            {
+                var task = Task.Run(() =>
+                {
+                    try
+                    {
+                        item.Invoke();
+                    }
+                    catch (Exception e)
+                    {
+                        log.Error($"Tasks Run Error:{e.Message}", e);
+                    }
+                });
+                tasks.Add(task);
+            }
+            if (taskWait) Task.WaitAll(tasks.ToArray());
+            return tasks;
+        }
+
+
+        public static void WaitAll(List<Task> tasks)
+        {
+            Task.WaitAll(tasks.ToArray());
+        }
+    }
+}

+ 23 - 0
SKMC.API/Common/Tcp/ITcpProxy.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Tcp
+{
+    /// <summary>
+    /// TCP转发代理
+    /// </summary>
+    public interface ITcpProxy
+    {
+        void SetLocal(string localHost, int localPort);
+
+        void SetTarget(string targetHost, int targetPort);
+
+        void Listen(int size);
+
+        void Close();
+
+    }
+}

+ 42 - 0
SKMC.API/Common/Tcp/ITcpServer.cs

@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Tcp
+{
+    /// <summary>
+    /// TCP服务器接口
+    /// </summary>
+    public interface ITcpServer
+    {
+        /// <summary>
+        /// 设定服务参数
+        /// </summary>
+        /// <param name="address">Host地址</param>
+        /// <param name="port">Host端口</param>
+        /// <param name="keepAlived">是否长连接</param>
+        void SetHost(string address, int port, bool keepAlived);
+
+        /// <summary>
+        /// 开启服务 (需要开新线程)
+        /// </summary>
+        /// <param name="cacheSize">最大请求队列数</param>
+        /// <param name="worker">string输入输出工作</param>
+        void Listen(int cacheSize, Func<string, string> worker);
+
+        /// <summary>
+        /// 开启服务 (需要开新线程)
+        /// </summary>
+        /// <param name="cacheSize">最大请求队列数</param>
+        /// <param name="worker">socket数据处理工作</param>
+        void Listen(int cacheSize, Action<Socket> worker);
+
+        /// <summary>
+        /// 关闭服务
+        /// </summary>
+        void Close();
+    }
+}

+ 96 - 0
SKMC.API/Common/Tcp/TCPClient.cs

@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace SKMC.Api.Common.Tcp
+{
+    /// <summary>
+    /// TCP Socket客户端
+    /// </summary>
+    public class TCPClient
+    {
+
+        static readonly ILogger log = LogFactory.Get();
+
+        public int BufferSize { get; set; } = 1024 * 8;
+
+        public string Address { get; set; } = "127.0.0.1";
+
+        public int Port { get; set; }
+
+        public int Timeout { get; set; } = 10000;
+
+        public bool KeepAlived { get; set; }
+
+        public bool Logged { get; set; } = true;
+
+        private Socket clientSocket;
+
+        public TCPClient(string address, int port)
+        {
+            Address = address;
+            Port = port;
+        }
+
+        public void Connect()
+        {
+            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, Timeout);
+            clientSocket.Connect(new IPEndPoint(IPAddress.Parse(Address), Port));
+            log.Debug($"已连接至 {Address}:{Port}");
+        }
+
+        public bool IsConnected(int millSeconds)
+        {
+            if (clientSocket == null) return false;
+            //bool pollResult = clientSocket.Poll(1000 * millSeconds, SelectMode.SelectRead);
+            //return pollResult && clientSocket.Available == 0;
+            return true;
+        }
+
+
+        public void Close()
+        {
+            try
+            {
+                clientSocket.Shutdown(SocketShutdown.Both);
+                clientSocket.Close();
+            }
+            catch (Exception e)
+            {
+                log.Error("Socket连接关闭异常", e);
+            }
+        }
+
+
+        public void Clear()
+        {
+            if (clientSocket.Available > 0)
+            {
+                clientSocket.Receive(new byte[clientSocket.Available]);
+            }
+        }
+
+
+        public void Send(string msg, bool logged = true)
+        {
+            clientSocket.Send(Encoding.ASCII.GetBytes(msg));
+            if (Logged && logged) log.Debug($"已发送消息:\n{msg}");
+        }
+
+
+        public string Recevie(bool logged = true)
+        {
+            byte[] buffer = new byte[BufferSize];
+            int length = clientSocket.Receive(buffer);
+            string msg = Encoding.ASCII.GetString(buffer, 0, length);
+            if (Logged && logged) log.Debug($"接收到消息:\n{msg}");
+            return msg;
+        }
+
+        public override string ToString() => $"address: {Address}, port: {Port}, keepAlive: {KeepAlived}, timeout: {Timeout}";
+    }
+}

+ 155 - 0
SKMC.API/Common/Tcp/TcpClientBase.cs

@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Tcp
+{
+    public class TcpClientBase
+    {
+        protected static readonly ILogger log = LogFactory.Get();
+
+        public string Address { get; set; } = "127.0.0.1";
+
+        public int Port { get; set; }
+
+        public int TIMEOUT { get; set; } = 5000;
+
+        // 是否长连接
+        public bool KeepAlived { get; set; } = false;
+
+        // 测试模式(空跑模式)下, 不等待接收响应
+        public bool TestMode { get; set; } = false;
+
+        // 是否开启
+        public bool Enabled { get; set; } = true;
+
+        public bool Logged { get; set; } = true;
+
+        // 是否运行中, 结束运行可放弃等待服务端响应数据
+        public bool Running { get; set; }
+
+        private TCPClient client;
+
+
+        public TcpClientBase(string address, int port)
+        {
+            Address = address;
+            Port = port;
+            client = new TCPClient(Address, Port) { KeepAlived = KeepAlived };
+        }
+
+        /// <summary>
+        /// 保持连接, 如果连接已中断则重新连接
+        /// </summary>
+        /// <param name="mills">验证连接的超时时间(ms)</param>
+        /// <returns></returns>
+        public TCPClient Connect(int mills)
+        {
+            if (!Enabled) return null;
+            if (!KeepAlived || !client.IsConnected(mills))
+            {
+                client.Connect();
+            }
+            return client;
+        }
+
+        public TCPClient Connect()
+        {
+            if (!Enabled) return null;
+            if (client != null) client.Connect();
+            return client;
+        }
+
+        public void Reset(int index)
+        {
+            if (client != null) client.Clear();
+            Running = false;
+        }
+
+        /// <summary>
+        /// 请求并获取结果
+        /// 请求后阻塞获取确认消息
+        /// 再异步接收结果
+        /// </summary>
+        /// <param name="requestMsg">请求消息</param>
+        /// <param name="responseAction">响应结果解析</param>
+        /// <returns></returns>
+        public Task RequestAndResult(string requestMsg, bool sync = false, Action<string> responseAction = null, Action<string> resultAction = null)
+        {
+            string response = null;
+            string result = null;
+            try
+            {
+                client = Connect(100);
+                Reset(0);
+
+                client.Send(requestMsg, Logged);
+                response = client.Recevie(Logged);
+
+                if (sync)
+                {
+                    resultAction?.Invoke(response);
+                    if (!KeepAlived) client.Close();
+                }
+                else
+                {
+                    // 异步模式, 视觉采图响应
+                    try
+                    {
+                        responseAction?.Invoke(response);
+                    }
+                    catch (Exception e)
+                    {
+                        throw new Exception($"Response Parse(Aync) Error", e);
+                    }
+                }
+
+                if (sync) return null;
+
+                return Task.Run(() =>
+                {
+                    try
+                    {
+                        result = client.Recevie(Logged);
+                        resultAction?.Invoke(result);
+                        if (!KeepAlived) client.Close();
+                    }
+                    catch (Exception e)
+                    {
+                        throw new Exception($"Result Parse(Aync) Error", e);
+                    }
+                });
+            }
+            catch (SocketException ex)
+            {
+                switch ((SocketError)ex.ErrorCode)
+                {
+                    case SocketError.TimedOut:
+                        throw new Exception($"接收超时: [{Address} : {Port}]", ex);
+
+                    case SocketError.ConnectionRefused:
+                        throw new Exception($"连接被拒绝: [{Address} : {Port}]", ex);
+
+                    case SocketError.HostNotFound:
+                        throw new Exception($"地址未找到: [{Address} : {Port}]", ex);
+
+                    case SocketError.NetworkDown:
+                        throw new Exception($"网络不可用: [{Address} : {Port}]", ex);
+
+                    case SocketError.AddressAlreadyInUse:
+                        throw new Exception($"地址已被使用: [{Address} : {Port}]", ex);
+
+                    default:
+                        throw new Exception($"其他Socket异常: [{Address} : {Port}]", ex);
+                }
+            }
+            catch (Exception e)
+            {
+                throw e;
+            }
+        }
+    }
+}

+ 52 - 0
SKMC.API/Common/Types/BlockingQueue.cs

@@ -0,0 +1,52 @@
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace SKMC.Api.Common.Types
+{
+    /// <summary>
+    /// 阻塞队列
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class BlockingQueue<T>
+    {
+        private readonly ConcurrentQueue<T> queue;
+
+        private readonly ManualResetEvent manualResetEvent;
+
+        public BlockingQueue()
+        {
+            queue = new ConcurrentQueue<T>();
+            manualResetEvent = new ManualResetEvent(false);
+        }
+
+        public int Count { get => queue.Count; }
+
+        public void Add(T item)
+        {
+            queue.Enqueue(item);
+            manualResetEvent.Set();
+        }
+
+        public T Take()
+        {
+            bool isEmpty = false;
+            T item = default(T);
+            while (true)
+            {
+                if (queue.Count > 0)
+                {
+                    queue.TryDequeue(out item);
+                    manualResetEvent.Reset();
+                }
+                else
+                {
+                    isEmpty = true;
+                }
+                if (item != null) return item;
+                if (isEmpty) manualResetEvent.WaitOne();
+            }
+        }
+
+        public void Stop() => manualResetEvent.Set();
+    }
+}

+ 51 - 0
SKMC.API/Common/Types/BoundedQueue.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Types
+{
+    /// <summary>
+    /// 边界队列
+    /// 添加新元素时检查是否达到上限, 如果达到则先移除最早的一个元素
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class BoundedQueue<T>
+    {
+        private readonly Queue<T> queue;
+        private readonly int maxSize;
+
+        public BoundedQueue(int size)
+        {
+            maxSize = size;
+            queue = new Queue<T>();
+        }
+
+        public void Enqueue(T item)
+        {
+            if (queue.Count >= maxSize)
+            {
+                queue.Dequeue();
+            }
+            queue.Enqueue(item);
+        }
+
+        public T Dequeue()
+        {
+            return queue.Dequeue();
+        }
+
+        public T Peek()
+        {
+            return queue.Peek();
+        }
+
+        public int Count
+        {
+            get { return queue.Count; }
+        }
+
+        public T[] ToArray() => queue.ToArray();
+    }
+}

+ 245 - 0
SKMC.API/Common/Types/ConcurrentList.cs

@@ -0,0 +1,245 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace SKMC.Api.Common.Types
+{
+    public class ConcurrentList<T> : IList<T>, IList
+    {
+        private readonly List<T> underlyingList = new List<T>();
+        private readonly object syncRoot = new object();
+        private readonly ConcurrentQueue<T> underlyingQueue;
+        private bool requiresSync;
+        private bool isDirty;
+
+        public ConcurrentList()
+        {
+            underlyingQueue = new ConcurrentQueue<T>();
+        }
+
+        public ConcurrentList(IEnumerable<T> items)
+        {
+            underlyingQueue = new ConcurrentQueue<T>(items);
+        }
+
+        private void UpdateLists()
+        {
+            if (!isDirty)
+                return;
+            lock (syncRoot)
+            {
+                requiresSync = true;
+                T temp;
+                while (underlyingQueue.TryDequeue(out temp))
+                    underlyingList.Add(temp);
+                requiresSync = false;
+            }
+        }
+
+        public IEnumerator<T> GetEnumerator()
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.GetEnumerator();
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public void Add(T item)
+        {
+            if (requiresSync)
+                lock (syncRoot)
+                    underlyingQueue.Enqueue(item);
+            else
+                underlyingQueue.Enqueue(item);
+            isDirty = true;
+        }
+
+        public int Add(object value)
+        {
+            if (requiresSync)
+                lock (syncRoot)
+                    underlyingQueue.Enqueue((T)value);
+            else
+                underlyingQueue.Enqueue((T)value);
+            isDirty = true;
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.IndexOf((T)value);
+            }
+        }
+
+        public bool Contains(object value)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.Contains((T)value);
+            }
+        }
+
+        public int IndexOf(object value)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.IndexOf((T)value);
+            }
+        }
+
+        public void Insert(int index, object value)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.Insert(index, (T)value);
+            }
+        }
+
+        public void Remove(object value)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.Remove((T)value);
+            }
+        }
+
+        public void RemoveAt(int index)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.RemoveAt(index);
+            }
+        }
+
+        T IList<T>.this[int index]
+        {
+            get
+            {
+                lock (syncRoot)
+                {
+                    UpdateLists();
+                    return underlyingList[index];
+                }
+            }
+            set
+            {
+                lock (syncRoot)
+                {
+                    UpdateLists();
+                    underlyingList[index] = value;
+                }
+            }
+        }
+
+        object IList.this[int index]
+        {
+            get { return ((IList)this)[index]; }
+            set { ((IList)this)[index] = (T)value; }
+        }
+
+        public bool IsReadOnly
+        {
+            get { return false; }
+        }
+
+        public bool IsFixedSize
+        {
+            get { return false; }
+        }
+
+        public void Clear()
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.Clear();
+            }
+        }
+
+        public bool Contains(T item)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.Contains(item);
+            }
+        }
+
+        public void CopyTo(T[] array, int arrayIndex)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.CopyTo(array, arrayIndex);
+            }
+        }
+
+        public bool Remove(T item)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.Remove(item);
+            }
+        }
+
+        public void CopyTo(Array array, int index)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.CopyTo((T[])array, index);
+            }
+        }
+
+        public int Count
+        {
+            get
+            {
+                lock (syncRoot)
+                {
+                    UpdateLists();
+                    return underlyingList.Count;
+                }
+            }
+        }
+
+        public object SyncRoot
+        {
+            get { return syncRoot; }
+        }
+
+        public bool IsSynchronized
+        {
+            get { return true; }
+        }
+
+        public int IndexOf(T item)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                return underlyingList.IndexOf(item);
+            }
+        }
+
+        public void Insert(int index, T item)
+        {
+            lock (syncRoot)
+            {
+                UpdateLists();
+                underlyingList.Insert(index, item);
+            }
+        }
+    }
+}

+ 44 - 0
SKMC.API/Common/Types/MessageQueue.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Types
+{
+    public class MessageQueue<T>
+    {
+
+        private static MessageQueue<T> instance;
+
+        private readonly BlockingQueue<T> queue = new BlockingQueue<T>();
+
+        private MessageQueue() { }
+
+        public static MessageQueue<T> Instance()
+        {
+            if (instance == null) instance = new MessageQueue<T>();
+            return instance;
+        }
+
+        public int Count { get => queue.Count; }
+
+        public void Put(T t)
+        {
+            queue.Add(t);
+        }
+
+        public T Get()
+        {
+            return queue.Take();
+        }
+
+        public void Clear()
+        {
+            while (queue.Count > 0)
+            {
+                Get();
+            }
+        }
+    }
+}

+ 68 - 0
SKMC.API/Common/Types/ObservableCollectionEx.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Types
+{
+    /// <summary>
+    /// Expanded ObservableCollection to include some List<T> Methods
+    /// </summary>
+    [Serializable]
+    public class ObservableCollectionEx<T> : ObservableCollection<T>
+    {
+
+        /// <summary>
+        /// Constructors
+        /// </summary>
+        public ObservableCollectionEx() : base() { }
+        public ObservableCollectionEx(List<T> l) : base(l) { }
+        public ObservableCollectionEx(IEnumerable<T> l) : base(l) { }
+
+        /// <summary>
+        /// Sorts the items of the collection in multiply keys.
+        /// </summary>
+        /// <param name="sortExpressions"></param>
+        public void Sort(params (Func<T, object> keySelector, bool ascending)[] sortExpressions)
+        {
+            if (sortExpressions == null || sortExpressions.Length == 0)
+                return;
+
+            // Get the sorted items as a list
+            var sortedItems = this.ToList();
+
+            IOrderedEnumerable<T> orderedEnumerable = null;
+            for (int i = 0; i < sortExpressions.Length; i++)
+            {
+                var sortExpression = sortExpressions[i];
+                if (i == 0)
+                {
+                    orderedEnumerable = sortExpression.ascending
+                        ? sortedItems.OrderBy(sortExpression.keySelector)
+                        : sortedItems.OrderByDescending(sortExpression.keySelector);
+                }
+                else
+                {
+                    orderedEnumerable = sortExpression.ascending
+                        ? orderedEnumerable.ThenBy(sortExpression.keySelector)
+                        : orderedEnumerable.ThenByDescending(sortExpression.keySelector);
+                }
+            }
+
+            sortedItems = orderedEnumerable.ToList();
+
+            // Reorder the collection using Move
+            for (int i = 0; i < sortedItems.Count; i++)
+            {
+                var item = sortedItems[i];
+                var currentIndex = IndexOf(item);
+                if (currentIndex != i)
+                {
+                    Move(currentIndex, i);
+                }
+            }
+        }
+    }
+}

+ 231 - 0
SKMC.API/Common/Types/ObservableConcurrentList.cs

@@ -0,0 +1,231 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Windows;
+
+namespace SKMC.Api.Common.Types
+{
+    public class ObservableConcurrentList<T> : IList, IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
+    {
+        private readonly System.Windows.Threading.Dispatcher _context;
+        private readonly IList<T> _list = new List<T>();
+        private readonly object _lock = new object();
+        private T[] _snapshot;
+
+        public ObservableConcurrentList()
+        {
+            _context = Application.Current?.Dispatcher;
+
+            UpdateSnapshot();
+
+            //SuppressNotifications = suppressNotifications;
+        }
+
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        private void UpdateSnapshot()
+        {
+            lock (_lock) //precautionary; should be re-entry
+            {
+                Interlocked.Exchange(ref _snapshot, _list.ToArray());
+            }
+        }
+
+        private void Notify(NotifyCollectionChangedEventArgs args)
+        {
+            if (_context == null)
+            {
+                InvokeCollectionChanged(args);
+            }
+            else
+            {
+                _context.InvokeAsync(() => InvokeCollectionChanged(args));
+            }
+        }
+
+        private void InvokeCollectionChanged(NotifyCollectionChangedEventArgs args)
+        {
+            CollectionChanged?.Invoke(this, args);
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
+        }
+
+        #region IEnumerable
+        public IEnumerator<T> GetEnumerator()
+        {
+            var localSnapshot = _snapshot; //create local variable to protect enumerator, if class member (_snapshot) should be changed/replaced while iterating
+            return ((IEnumerable<T>)localSnapshot).GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+        #endregion
+
+        #region ICollection<T>
+        public void Add(T item)
+        {
+            lock (_lock)
+            {
+                _list.Add(item);
+                UpdateSnapshot();
+
+                Notify(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _list.Count - 1));
+            }
+        }
+
+        public bool Contains(T item)
+        {
+            return _snapshot.Contains(item);
+        }
+
+        public void CopyTo(T[] array, int arrayIndex)
+        {
+            _snapshot.CopyTo(array, arrayIndex);
+        }
+
+        public bool Remove(T item)
+        {
+            lock (_lock)
+            {
+                var index = _list.IndexOf(item);
+                if (index > -1)
+                {
+                    if (_list.Remove(item))
+                    {
+                        UpdateSnapshot();
+
+                        Notify(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        }
+
+        public void Clear()
+        {
+            lock (_lock)
+            {
+                _list.Clear();
+                UpdateSnapshot();
+
+                Notify(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+            }
+        }
+
+        public bool IsReadOnly => false;
+
+        #endregion
+
+        #region IList<T>
+
+        public int IndexOf(T item)
+        {
+            return Array.IndexOf(_snapshot, item);
+        }
+
+        public void Insert(int index, T item)
+        {
+            lock (_lock)
+            {
+                _list.Insert(index, item);
+                UpdateSnapshot();
+
+                Notify(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
+            }
+        }
+
+        public void RemoveAt(int index)
+        {
+            lock (_lock)
+            {
+                var item = _list[index];
+                _list.RemoveAt(index);
+                UpdateSnapshot();
+
+                Notify(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
+            }
+        }
+
+
+        public T this[int index]
+        {
+            get => _snapshot[index];
+            set
+            {
+                lock (_lock)
+                {
+                    var item = _list[index];
+                    _list[index] = value;
+                    UpdateSnapshot();
+
+                    Notify(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, item, index));
+                }
+            }
+        }
+        #endregion
+
+        #region ICollection (explicit)
+        void ICollection.CopyTo(Array array, int index)
+        {
+            CopyTo((T[])array, index);
+        }
+
+        public int Count => _snapshot.Length;
+
+        object ICollection.SyncRoot => this; //https://stackoverflow.com/questions/728896/whats-the-use-of-the-syncroot-pattern/728934#728934
+
+        bool ICollection.IsSynchronized => false; //https://stackoverflow.com/questions/728896/whats-the-use-of-the-syncroot-pattern/728934#728934
+
+        #endregion
+
+        #region IList (explicit)
+
+        object IList.this[int index]
+        {
+            get => ((IList<T>)this)[index];
+            set => ((IList<T>)this)[index] = (T)value;
+        }
+
+        int IList.Add(object value)
+        {
+            lock (_lock)
+            {
+                Add((T)value);
+
+                return _list.Count - 1;
+            }
+        }
+
+        bool IList.Contains(object value)
+        {
+            return Contains((T)value);
+        }
+
+        int IList.IndexOf(object value)
+        {
+            return IndexOf((T)value);
+        }
+
+        void IList.Insert(int index, object value)
+        {
+            Insert(index, (T)value);
+        }
+
+        bool IList.IsFixedSize => false;
+
+        void IList.Remove(object value)
+        {
+            Remove((T)value);
+        }
+        #endregion
+    }
+}

+ 86 - 0
SKMC.API/Common/Types/TimestampSet.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Common.Types
+{
+    /// <summary>
+    /// 包含时间戳的数据集
+    /// </summary>
+    public class TimestampSet
+    {
+
+        private readonly int capacity;
+        private readonly (long Ticks, int Value)[] buffer;
+        private int currentIndex;
+        private long oldestTicks;
+        private readonly object _lock = new object();
+
+        public TimestampSet(int capacity)
+        {
+            this.capacity = capacity;
+            buffer = new (long, int)[capacity];
+            currentIndex = 0;
+            oldestTicks = long.MaxValue;
+        }
+
+        /// <summary>
+        /// 添加与时间戳相关的数据
+        /// </summary>
+        /// <param name="ticks"></param>
+        /// <param name="value"></param>
+        public void Add(long ticks, int value)
+        {
+            lock (_lock)
+            {
+                buffer[currentIndex] = (ticks, value);
+                currentIndex = (currentIndex + 1) % capacity;
+
+                if (currentIndex == 0)
+                {
+                    oldestTicks = buffer[currentIndex].Ticks;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 添加当前"时间戳"的数据, 该"时间戳"为简化版: ticks/10000
+        /// </summary>
+        /// <param name="value"></param>
+        public void Add(int value)
+        {
+            Add(DateTime.Now.Ticks / 10000, value);
+        }
+
+        /// <summary>
+        /// 获取最新的一个数值
+        /// </summary>
+        /// <returns></returns>
+        public int Take() => (currentIndex > 0) ? buffer[currentIndex - 1].Value : buffer[capacity - 1].Value;
+
+        /// <summary>
+        /// 获取某个时间戳之后的所有数据
+        /// </summary>
+        /// <param name="ticks">该"时间戳"为简化版: ticks/10000</param>
+        /// <returns></returns>
+        public List<int> GetDataAfter(long ticks)
+        {
+            List<int> result = new List<int>();
+
+            lock (_lock)
+            {
+                for (int i = 0; i < capacity; i++)
+                {
+                    var entry = buffer[i];
+                    if (entry.Ticks > ticks)
+                    {
+                        result.Add(entry.Value);
+                    }
+                }
+            }
+            return result;
+        }
+    }
+}

+ 23 - 0
SKMC.API/Device/Adapter/Vision/IResultParser.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Adapter.Vision
+{
+    /// <summary>
+    /// 结果字符串解析接口
+    /// </summary>
+    public interface IResultParser<T>
+    {
+        /// <summary>
+        /// 将结果字符串解析并注入到目标对象中
+        /// </summary>
+        /// <param name="t">目标对象</param>
+        /// <param name="resultMsg">结果字符串</param>
+        /// <returns></returns>
+        void Parse(ref T t, string resultMsg);
+
+    }
+}

+ 141 - 0
SKMC.API/Device/Adapter/Vision/SKVisionClient.cs

@@ -0,0 +1,141 @@
+using SKMC.Api.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Adapter.Vision
+{
+    /// <summary>
+    /// 视觉通讯协议基础客户端
+    /// </summary>
+    public class SKVisionClient
+    {
+        private readonly static ILogger log = LogFactory.Get();
+
+        private readonly SKVisionClientBase clientBase;
+
+        public SKVisionClient(int port, int timeout)
+        {
+            clientBase = new SKVisionClientBase(port, timeout);
+        }
+
+        /// <summary>
+        /// 发送请求不接收结果
+        /// </summary>
+        /// <param name="moduleCode">模块码</param>
+        /// <param name="functionCode">功能码</param>
+        /// <param name="productCode">产品码</param>
+        /// <param name="parameters">请求参数</param>
+        public Task Request(string moduleCode, string functionCode, string productCode, bool isOnlySend,
+            List<string> parameters = null)
+        {
+            string timeStamp = DateTime.Now.ToString("yyMMddHHmmssfff");
+            SKVisionProtocol skVisionRequest = new SKVisionProtocol
+            {
+                Direction = "T",
+                ModuleCode = moduleCode,
+                FuncCode = functionCode,
+                ProductCode = productCode,
+                Timestamp = timeStamp,
+                BodyDomains = parameters
+            };
+            clientBase.Request(skVisionRequest.ToRequest(), isOnlySend);
+            return default;
+        }
+
+        /// <summary>
+        /// 发送请求并接收结果 (同步模式)
+        /// </summary>
+        /// <param name="moduleCode">模块码</param>
+        /// <param name="functionCode">功能码</param>
+        /// <param name="productCode">产品码</param>
+        /// <param name="parameters">请求参数</param>
+        /// <param name="resultAction">解析函数</param>
+        public Task Request(string moduleCode, string functionCode, string productCode,
+            List<string> parameters = null, Action<SKVisionProtocol> resultAction = null)
+        {
+            string timeStamp = DateTime.Now.ToString("yyMMddHHmmssfff");
+            SKVisionProtocol skVisionRequest = new SKVisionProtocol
+            {
+                Direction = "T",
+                ModuleCode = moduleCode,
+                FuncCode = functionCode,
+                ProductCode = productCode,
+                Timestamp = timeStamp,
+                BodyDomains = parameters
+            };
+            clientBase.Request(skVisionRequest.ToRequest(),
+                responseAction: (response) =>
+                {
+                    resultAction?.Invoke(skVisionRequest.FromResponse(response));
+                });
+            return default;
+        }
+
+        /// <summary>
+        /// 发送请求并接收结果 (异步模式)
+        /// </summary>
+        /// <param name="moduleCode"></param>
+        /// <param name="functionCode"></param>
+        /// <param name="productCode"></param>
+        /// <param name="parameters"></param>
+        /// <param name="responseAction"></param>
+        /// <param name="resultAction"></param>
+        /// <returns></returns>
+        public Task Request(string moduleCode, string functionCode, string productCode,
+            List<string> parameters = null, Action<SKVisionProtocol> responseAction = null, Action<SKVisionProtocol> resultAction = null)
+        {
+            string timeStamp = DateTime.Now.ToString("yyMMddHHmmssfff");
+            // 是否粘包
+            bool isMultiPacks = false;
+            SKVisionProtocol skVisionRequest = new SKVisionProtocol
+            {
+                Direction = "T",
+                ModuleCode = moduleCode,
+                FuncCode = functionCode,
+                ProductCode = productCode,
+                Timestamp = timeStamp,
+                BodyDomains = parameters
+            };
+            clientBase.Request(skVisionRequest.ToRequest(),
+                responseAction: (response) =>
+                {
+                    SKVisionProtocol skVisionProtocol = skVisionRequest.FromResponse(response);
+                    responseAction?.Invoke(skVisionRequest.FromResponse(response));
+                    // 有粘包
+                    if (skVisionProtocol.SKVisionSubProtocol != null)
+                    {
+                        log.Debug("粘包处理...");
+                        resultAction?.Invoke(skVisionProtocol.SKVisionSubProtocol);
+                        isMultiPacks = true;
+                    }
+                });
+            if (!isMultiPacks)
+            {
+                return Task.Run(() =>
+                {
+                    string result = null;
+                    try
+                    {
+                        result = clientBase.GetResult();
+                    }
+                    catch (Exception e)
+                    {
+                        log.Error($"获取视觉结果异常: {e.Message}", e);
+                    }
+                    try
+                    {
+                        resultAction?.Invoke(skVisionRequest.FromResponse(result));
+                    }
+                    catch (Exception e)
+                    {
+                        log.Error($"处理视觉结果异常: {e.Message}", e);
+                    }
+                });
+            }
+            return default;
+        }
+    }
+}

+ 170 - 0
SKMC.API/Device/Adapter/Vision/SKVisionClientBase.cs

@@ -0,0 +1,170 @@
+using SKMC.Api.Common;
+using SKMC.Api.Common.Tcp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Adapter.Vision
+{
+    /// <summary>
+    /// 视觉客户端通用基类
+    /// </summary>
+    public class SKVisionClientBase
+    {
+        protected static readonly ILogger log = LogFactory.Get();
+
+        public string Address { get; set; } = "127.0.0.1";
+
+        public int Port { get; set; }
+
+        public int TIMEOUT { get; set; } = 5000;
+
+        // 测试模式(空跑模式)下, 不等待接收响应
+        public bool TestMode { get; set; } = false;
+
+        // 是否开启
+        public bool Enabled { get; set; } = true;
+
+        // 是否运行中, 结束运行可放弃等待服务端响应数据
+        public bool Running { get; set; }
+
+        private TCPClient client;
+
+
+        public SKVisionClientBase(int port, int timeout)
+        {
+            //Enabled = Process_Vision_Enabled == 1;
+            //Address = Vision_Address;
+            TIMEOUT = timeout;
+            Port = port;
+            client = new TCPClient(Address, Port) { KeepAlived = true };
+        }
+
+        /// <summary>
+        /// 保持连接, 如果连接已中断则重新连接
+        /// </summary>
+        /// <param name="mills">验证连接的超时时间(ms)</param>
+        /// <returns></returns>
+        public TCPClient Connect(int mills)
+        {
+            if (!Enabled) return null;
+            if (!client.IsConnected(mills))
+            {
+                client.Connect();
+            }
+            return client;
+        }
+
+        /// <summary>
+        /// 连接新的连接
+        /// </summary>
+        /// <returns></returns>
+        public TCPClient Connect()
+        {
+            if (!Enabled) return null;
+            if (client != null) client.Connect();
+            return client;
+        }
+
+        public void Reset(int index)
+        {
+            if (client != null) client.Clear();
+            Running = false;
+        }
+
+        /// <summary>
+        /// 请求并获取结果
+        /// 请求后阻塞获取确认消息
+        /// </summary>
+        /// <param name="requestMsg">请求消息</param>
+        /// <param name="responseAction">响应结果解析</param>
+        /// <returns></returns>
+        public void Request(string requestMsg, Action<string> responseAction = null)
+        {
+            try
+            {
+                client = Connect(100);
+                Reset(0);
+            }
+            catch (Exception e)
+            {
+                throw new VisionException { Type = VisionException.Conn_Fail, Source = e.Message };
+            }
+            try
+            {
+                client.Send(requestMsg);
+            }
+            catch (Exception e)
+            {
+                log.Error("send failed", e);
+                client = Connect();
+                client.Send(requestMsg);
+            }
+            string response = null;
+            try
+            {
+                response = client.Recevie();
+            }
+            catch (Exception e)
+            {
+                log.Error(e.Message);
+                throw new VisionException { Type = VisionException.Receive_Timeout, Source = e.Message };
+            }
+
+            try
+            {
+                responseAction?.Invoke(response);
+            }
+            catch (Exception e)
+            {
+                log.Error(e.Message);
+                throw new VisionException { Type = VisionException.Parse_Error, Source = e.Message };
+            }
+        }
+
+        /// <summary>
+        /// 请求不获取结果
+        /// 请求后不阻塞获取确认消息
+        /// </summary>
+        /// <param name="requestMsg">请求消息</param>
+        /// <returns></returns>
+        public void Request(string requestMsg, bool isOnlySend = true)
+        {
+            try
+            {
+                client = Connect(100);
+                Reset(0);
+            }
+            catch (Exception e)
+            {
+                log.Error(e.Message);
+                throw new VisionException { Type = VisionException.Conn_Fail, Source = e.Message };
+            }
+            try
+            {
+                client.Send(requestMsg);
+            }
+            catch (Exception e)
+            {
+                log.Error("send failed", e);
+                client = Connect();
+                client.Send(requestMsg);
+            }
+        }
+
+        public string GetResult()
+        {
+            try
+            {
+                return client.Recevie();
+            }
+            catch (Exception e)
+            {
+                log.Error(e.Message);
+                throw new VisionException { Type = VisionException.Receive_Timeout, Source = e.Message };
+            }
+        }
+    }
+}

+ 159 - 0
SKMC.API/Device/Adapter/Vision/SKVisionProtocol.cs

@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Adapter.Vision
+{
+    /// <summary>
+    /// SKVision通讯协议(V0)
+    /// </summary>
+    public class SKVisionProtocol
+    {
+        /// <summary>
+        /// 协议头
+        /// </summary>
+        public string Protocol { get; } = "SKV0";
+
+        /// <summary>
+        /// 请求方向, 请求:T、响应:R
+        /// </summary>
+        public string Direction { get; set; }
+
+        /// <summary>
+        /// 模块码
+        /// </summary>
+        public string ModuleCode { get; set; }
+
+        /// <summary>
+        /// 功能码
+        /// </summary>
+        public string FuncCode { get; set; }
+
+        /// <summary>
+        /// 产品码或二维码
+        /// </summary>
+        public string ProductCode { get; set; }
+
+        /// <summary>
+        /// 时间戳, 格式为:yyMMddHHmmssfff
+        /// </summary>
+        public string Timestamp { get; set; }
+
+        /// <summary>
+        /// 请求设置码
+        /// </summary>
+        public string OptionCode { get; set; } = "0000";
+
+        /// <summary>
+        /// 响应结果码
+        /// </summary>
+        public string ResultCode { get; set; }
+
+        /// <summary>
+        /// 报文体内容字段集
+        /// </summary>
+        public List<string> BodyDomains { get; set; }
+
+        /// <summary>
+        /// SKVision通讯协议子对象
+        /// </summary>
+        public SKVisionProtocol SKVisionSubProtocol { get; set; }
+
+        /// <summary>
+        /// 创建请求报文
+        /// </summary>
+        /// <returns></returns>
+        public string ToRequest()
+        {
+            string head = $"{Protocol},{Direction},{ModuleCode},{FuncCode},{ProductCode},{Timestamp},{OptionCode}";
+            if (BodyDomains == null)
+            {
+                return $"{head},#,,$";
+            }
+            return $"{head},#,{string.Join(",", BodyDomains.ToArray())},$";
+        }
+
+        /// <summary>
+        /// 解析响应报文并验证
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name=""></param>
+        /// <returns></returns>
+        public SKVisionProtocol FromResponse(string response, bool validate = true)
+        {
+            response = response.Trim();
+            int bodyStart = response.IndexOf("#,");
+            int bodyEnd = response.IndexOf(",$");
+
+            SKVisionProtocol skvpReponse = null;
+            if (bodyStart > 0 && bodyEnd > 0)
+            {
+                string head = response.Substring(0, bodyStart);
+                string body = response.Substring(bodyStart + 2, bodyEnd - bodyStart - 2);
+                skvpReponse = ParseContent(head, body, validate); 
+            }
+            // 判断是否有粘包
+            int subBodyEnd = response.IndexOf(",$", bodyEnd + 1);
+            int subBodyStart = response.IndexOf("#,", bodyEnd + 1);
+            if (subBodyStart > 0 && subBodyEnd > 0)
+            {
+                string subHead = response.Substring(bodyEnd + 1, subBodyStart - bodyEnd - 2);
+                string subBody = response.Substring(subBodyStart + 2, subBodyEnd - subBodyStart - 2);
+                skvpReponse.SKVisionSubProtocol = ParseContent(subHead, subBody, validate);
+            }
+            if (skvpReponse != null) return skvpReponse;
+            throw new VisionException { Type = VisionException.Parse_Error, Detail = $"视觉系统返回数据格式异常" };
+        }
+
+        /// <summary>
+        /// 内容解析
+        /// </summary>
+        /// <param name="head"></param>
+        /// <param name="body"></param>
+        /// <param name="validate"></param>
+        /// <returns></returns>
+        private SKVisionProtocol ParseContent(string head, string body, bool validate = true)
+        {
+            SKVisionProtocol skvpReponse = new SKVisionProtocol
+            {
+                BodyDomains = new List<string>()
+            };
+            try
+            {
+                string[] heads = head.Split(',');
+                string[] parts = body.Split(',');
+                skvpReponse.Direction = heads[1];
+                skvpReponse.ModuleCode = heads[2];
+                skvpReponse.FuncCode = heads[3];
+                skvpReponse.ProductCode = heads[4];
+                skvpReponse.Timestamp = heads[5];
+                skvpReponse.ResultCode = heads[6];
+                foreach (string part in parts)
+                {
+                    skvpReponse.BodyDomains.Add(part);
+                }
+            }
+            catch (Exception e)
+            {
+                throw new VisionException { Type = VisionException.Parse_Error, Detail = $"视觉系统返回数据解析异常: {e.Message}" };
+            }
+            if (validate)
+            {
+                // if (!skvpReponse.Timestamp.Equals(Timestamp) || !skvpReponse.ModuleCode.Equals(ModuleCode) ||
+                // !skvpReponse.FuncCode.Equals(FuncCode) || !skvpReponse.ProductCode.Equals(ProductCode))
+                if (!skvpReponse.Timestamp.Equals(Timestamp) || !skvpReponse.ModuleCode.Equals(ModuleCode) ||
+                !skvpReponse.FuncCode.Equals(FuncCode))
+                {
+                    throw new VisionException { Type = VisionException.Result_Error, Detail = $"视觉系统返回数据验证异常" };
+                }
+            }
+            if (!skvpReponse.ResultCode.Equals("0000"))
+            {
+                throw new VisionException { Type = VisionException.Result_Error, Detail = $"视觉系统异常, Code: {skvpReponse.ResultCode}" };
+            }
+            return skvpReponse;
+        }
+    }
+}

+ 37 - 0
SKMC.API/Device/Adapter/Vision/VisionException.cs

@@ -0,0 +1,37 @@
+using SKMC.Api.Common.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Adapter.Vision
+{
+    public class VisionException : ExceptionBase
+    {
+        /// <summary>
+        /// 视觉系统初始化失败 2177
+        /// </summary>
+        public static int Init_Fail = 0b_1000_1000_0001;
+
+        /// <summary>
+        /// 视觉系统连接异常 2178
+        /// </summary>
+        public static int Conn_Fail = 0b_1000_1000_0010;
+
+        /// <summary>
+        /// 视觉系统返回数据接收超时 2180
+        /// </summary>
+        public static int Receive_Timeout = 0b_1000_1000_0100;
+
+        /// <summary>
+        /// 视觉系统返回数据解析异常 2184
+        /// </summary>
+        public static int Parse_Error = 0b_1000_1000_1000;
+
+        /// <summary>
+        /// 视觉系统返回数据结果异常
+        /// </summary>
+        public static int Result_Error = 0b_1000_0100_0000;
+    }
+}

+ 31 - 0
SKMC.API/Device/Config/DeviceBaseConfig.cs

@@ -0,0 +1,31 @@
+using Prism.Mvvm;
+
+namespace SKMC.Api.Device.Config
+{
+    public class DeviceBaseConfig : BindableBase
+    {
+        private string _code;
+
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; RaisePropertyChanged(); }
+        }
+
+        private string _name;
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; RaisePropertyChanged(); }
+        }
+
+        private string _note;
+
+        public string Note
+        {
+            get { return _note; }
+            set { _note = value; RaisePropertyChanged(); }
+        }
+    }
+}

+ 18 - 0
SKMC.API/Device/Config/DeviceConfigStore.cs

@@ -0,0 +1,18 @@
+using System;
+using SKMC.Api.Common.Exceptions;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Config
+{
+    public abstract class DeviceConfigStore
+    {
+
+        /// <summary>
+        /// 异常配置集
+        /// </summary>
+        public List<ExceptionConfig> Exceptions { get; set; }
+    }
+}

+ 28 - 0
SKMC.API/Device/Config/DeviceConfiger.cs

@@ -0,0 +1,28 @@
+
+namespace SKMC.Api.Device.Config
+{
+
+    public class DeviceCatalogConfig : DeviceBaseConfig
+    {
+        public bool IsChecked { get; set; }
+    }
+
+    public class DeviceParamConfig : DeviceBaseConfig
+    {
+        private string _group;
+
+        public string Group
+        {
+            get { return _group; }
+            set { _group = value; RaisePropertyChanged(); }
+        }
+
+        private string _value;
+
+        public string Value
+        {
+            get { return _value; }
+            set { _value = value; RaisePropertyChanged(); }
+        }
+    }
+}

+ 16 - 0
SKMC.API/Device/Config/DeviceConstants.cs

@@ -0,0 +1,16 @@
+
+namespace SKMC.Api.Device.Config
+{
+    /// <summary>
+    /// 设备常量
+    /// </summary>
+    public class DeviceConstants
+    {
+        // 设备整机部分的Id
+        public static readonly int DeviceId_Machine = 1000;
+
+        // 三色灯部分的Id
+        public static readonly int DeviceId_Lamp = 1010;
+
+    }
+}

+ 13 - 0
SKMC.API/Device/Config/MachineConfigStore.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Config
+{
+    public abstract class MachineConfigStore
+    {
+
+    }
+}

+ 112 - 0
SKMC.API/Device/Config/ParamEnum.cs

@@ -0,0 +1,112 @@
+
+namespace SKMC.Api.Device.Config
+{
+    /// <summary>
+    /// 设备参数
+    /// </summary>
+    public enum ParamEnum
+    {
+        /// <summary>
+        /// 设备组
+        /// 10
+        /// </summary>
+        Device,
+
+        /// <summary>
+        /// 控制组(电机与IO)
+        /// 11
+        /// </summary>
+        Motion,
+
+        /// <summary>
+        /// 流程组
+        /// 12
+        /// </summary>
+        Process,
+
+        /// <summary>
+        /// 视觉组
+        /// 13
+        /// </summary>
+        Vision,
+
+        /// <summary>
+        /// 程序组
+        /// 14
+        /// </summary>
+        Client,
+
+        #region Device
+        /// <summary>
+        /// 设备运行模式 0:开发,1:上机
+        /// Id: 10000
+        /// </summary>
+        DEV_RUN_MODE,
+
+        #endregion
+
+        #region Motion
+        /// <summary>
+        /// 运动控制卡驱动
+        /// Id: 11000
+        /// </summary>
+        MOT_CARD_DRIVER,
+
+        /// <summary>
+        /// IO输入模块数量
+        /// Id: 11001
+        /// </summary>
+        MOT_DI_NUM,
+
+        /// <summary>
+        /// IO输出模块数量
+        /// Id: 11002
+        /// </summary>
+        MOT_DO_NUM,
+
+        /// <summary>
+        /// 每个IO模块的点位数
+        /// Id: 11003
+        /// </summary>
+        MOT_SITE_NUM,
+
+        /// <summary>
+        /// 运动监控CT
+        /// Id: 11010
+        /// </summary>
+        MOT_MONITOR_CT,
+
+        /// <summary>
+        /// 运动状态CT
+        /// Id: 11011
+        /// </summary>
+        MOT_STATUS_CT,
+
+        /// <summary>
+        /// 电机自动上使能
+        /// Id: 11020
+        /// </summary>
+        MOT_AXIS_AUTO,
+        #endregion
+
+
+        #region Process,
+        /// <summary>
+        /// 任务触发CT
+        /// Id: 12010
+        /// </summary>
+        PRC_TRIGGER_CT,
+
+        #endregion
+
+        #region Client
+        /// <summary>
+        /// 角色无操作后登出时间(单位秒)
+        /// Id: 14000
+        /// </summary>
+        CLT_LOGIN_TIMEOUT
+
+        #endregion
+
+    }
+}

+ 69 - 0
SKMC.API/Device/DeviceCacher.cs

@@ -0,0 +1,69 @@
+using SKMC.Api.Common.Exceptions;
+using SKMC.Api.Device.Config;
+using System.Collections.ObjectModel;
+
+namespace SKMC.Api.Device
+{
+    /// <summary>
+    /// 设备配置参数缓存器
+    /// </summary>
+    public abstract class DeviceCacher
+    {
+        // Module数据
+        public ObservableCollection<DeviceCatalogConfig> DeviceCatalogs { get; set; }
+
+        // Param数据
+        public ObservableCollection<DeviceParamConfig> DeviceParams { get; set; }
+
+        /// <summary>
+        /// 获取设备参数
+        /// </summary>
+        /// <param name="group"></param>
+        /// <param name="code"></param>
+        /// <returns></returns>
+        public abstract string GetDeviceParamValue(string group, string code);
+
+        /// <summary>
+        /// 获取设备参数
+        /// </summary>
+        /// <param name="groupEnum"></param>
+        /// <param name="codeEnum"></param>
+        /// <returns></returns>
+        public abstract string GetDeviceParamValue(ParamEnum groupEnum, ParamEnum codeEnum);
+
+        /// <summary>
+        /// 获取设备参数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="groupCode"></param>
+        /// <param name="paramCode"></param>
+        /// <returns></returns>
+        public abstract T GetDeviceParamValue<T>(string groupCode, string paramCode);
+
+        /// <summary>
+        /// 获取设备参数
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="groupEnum"></param>
+        /// <param name="codeEnum"></param>
+        /// <returns></returns>
+        public abstract T GetDeviceParamValue<T>(ParamEnum groupEnum, ParamEnum codeEnum);
+
+        /// <summary>
+        /// 获取异常配置对象
+        /// </summary>
+        /// <param name="code">异常码</param>
+        /// <param name="cloned">是否复制为新对象</param>
+        /// <returns></returns>
+        public abstract ExceptionConfig GetException(string code, bool cloned = false);
+
+        /// <summary>
+        /// 获取异常配置对象
+        /// </summary>
+        /// <param name="code">异常码</param>
+        /// <param name="cloned">是否复制为新对象</param>
+        /// <returns></returns>
+        public abstract ExceptionConfig GetException(int sysCode, bool cloned = false);
+
+    }
+}

+ 100 - 0
SKMC.API/Device/Machine/IMachineBoardControl.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Machine
+{
+    /// <summary>
+    /// 机台面板控制逻辑接口
+    /// </summary>
+    public interface IMachineBoardControl
+    {
+        /// <summary>
+        /// 是否启用
+        /// </summary>
+        bool Actived { get; set; }
+
+        /// <summary>
+        /// 是否告警中
+        /// </summary>
+        bool IsAlarm { get; set; }
+
+        /// <summary>
+        /// 设备启动动作
+        /// </summary>
+        Action StartAction { get; set; }
+
+        /// <summary>
+        /// 设备启动事件
+        /// </summary>
+        event Action StartEvent;
+
+        /// <summary>
+        /// 设备复位动作
+        /// </summary>
+        Action ResetAction { get; set; }
+
+        /// <summary>
+        /// 设备复位事件
+        /// </summary>
+        event Action ResetEvent;
+
+        /// <summary>
+        /// 设备暂停动作
+        /// </summary>
+        Action PauseAction { get; set; }
+
+        /// <summary>
+        /// 设备暂停单站动作
+        /// </summary>
+        Action<int> PauseOneAction { get; set; }
+
+        /// <summary>
+        /// 设备暂停事件
+        /// </summary>
+        event Action PauseEvent;
+
+        /// <summary>
+        /// 设备恢复运行动作
+        /// </summary>
+        Action ResumeAction { get; set; }
+
+        /// <summary>
+        /// 设备恢复运行事件
+        /// </summary>
+        event Action ResumeEvent;
+
+        /// <summary>
+        /// 设备清料停止动作
+        /// </summary>
+        Action ClearAction { get; set; }
+
+        /// <summary>
+        /// 设备清料停止事件
+        /// </summary>
+        event Action ClearEvent;
+
+        /// <summary>
+        /// 设备停止动作(退出自动流程, 并执行Stop动作)
+        /// </summary>
+        Action StopAction { get; set; }
+
+        /// <summary>
+        /// 设备停止事件
+        /// </summary>
+        event Action StopEvent;
+
+        /// <summary>
+        /// 设备退出动作(退出自动流程, 但不执行Stop动作)
+        /// </summary>
+        Action QuitAction { get; set; }
+
+        /// <summary>
+        /// 设备退出事件
+        /// </summary>
+        event Action QuitEvent;
+
+    }
+}

+ 31 - 0
SKMC.API/Device/Machine/IMachineButtonControl.cs

@@ -0,0 +1,31 @@
+using SKMC.Api.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Machine
+{
+    /// <summary>
+    /// 机台设备按钮控制接口
+    /// </summary>
+    public interface IMachineButtonControl
+    {
+        /// <summary>
+        /// 所有按钮开始监测
+        /// </summary>
+        void Start();
+
+        /// <summary>
+        /// 所有按钮停止监测
+        /// </summary>
+        void Stop();
+
+        /// <summary>
+        /// 所有按钮状态重置
+        /// </summary>
+        void Reset();
+
+    }
+}

+ 21 - 0
SKMC.API/Device/Machine/IMachineTowerLightControl.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Device.Machine
+{
+    /// <summary>
+    /// 机台三色灯(含蜂鸣器)的控制接口
+    /// </summary>
+    public interface IMachineTowerLightControl
+    {
+
+        /// <summary>
+        /// 根据设备状态自动匹配三色灯动作
+        /// </summary>
+        /// <param name="status">设备当前状态, 可参考MachineStatusEnum</param>
+        void Match(byte status);
+    }
+}

+ 43 - 0
SKMC.API/Device/Machine/MachineStatus.cs

@@ -0,0 +1,43 @@
+using Prism.Mvvm;
+using System;
+
+namespace SKMC.Api.Device.Machine
+{
+    /// <summary>
+    /// 机台运行状态
+    /// </summary>
+    public class MachineStatus : BindableBase
+    {
+
+        private static MachineStatus instance;
+
+        private MachineStatus() { }
+
+        public static MachineStatus Instance()
+        {
+            if (instance == null) instance = new MachineStatus();
+            return instance;
+        }
+
+        private byte _status = 0;
+
+        // 运行状态, 参考 DeviceStatusEnum
+        public byte Status
+        {
+            get { return _status; }
+            set
+            {
+                _status = value;
+                RaisePropertyChanged();
+                if (RaiseStatusAction != null) RaiseStatusAction.Invoke();
+            }
+        }
+
+        // 告警前的状态
+        public byte StatusBeforeAlarm { get; set; }
+
+        // 状态变更通知
+        public Action RaiseStatusAction { get; set; }
+
+    }
+}

+ 64 - 0
SKMC.API/Device/Machine/MachineStatusEnum.cs

@@ -0,0 +1,64 @@
+
+namespace SKMC.Api.Device.Machine
+{
+    /// <summary>
+    /// 机台运行状态枚举
+    /// </summary>
+    public enum MachineStatusEnum : byte
+    {
+        /// <summary>
+        /// 已停止自动运行
+        /// </summary>
+        STOP = 0,
+
+        /// <summary>
+        /// 已暂停自动运行
+        /// </summary>
+        PAUSE = 1,
+
+        /// <summary>
+        /// 复位中
+        /// </summary>
+        RESET = 2,
+
+        /// <summary>
+        /// 已复位
+        /// </summary>
+        READY = 3,
+
+        /// <summary>
+        /// 自动运行中
+        /// </summary>
+        START = 4,
+
+        /// <summary>
+        /// 报警中(普通报警暂停, 严重报警停止)
+        /// </summary>
+        ALARM = 5,
+
+        /// <summary>
+        /// 调试状态
+        /// </summary>
+        Debug = 6,
+
+        /// <summary>
+        /// 设备暂停中
+        /// </summary>
+        PAUSING = 7,
+
+        /// <summary>
+        /// 正在启动自动运行
+        /// </summary>
+        STARTING = 8,
+
+        /// <summary>
+        /// 正在结束自动运行
+        /// </summary>
+        STOPPING = 9,
+
+        /// <summary>
+        /// 清料状态
+        /// </summary>
+        CLEANING = 10
+    }
+}

+ 314 - 0
SKMC.API/Device/Material/Tray/Tray.cs

@@ -0,0 +1,314 @@
+using Prism.Mvvm;
+using SKMC.Api.Common;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+
+namespace SKMC.Api.Device.Material.Tray
+{
+    /// <summary>
+    /// 基础物料盘, 可放m*n个物料
+    /// </summary>
+    public class Tray : BindableBase
+    {
+        protected static ILogger log = LogFactory.Get();
+
+        /// <summary>
+        /// index:
+        /// 0 1 2 3 4 5
+        /// 6 7 8 9 ...
+        /// </summary>
+        public ObservableCollection<TraySlot> Slots { get; set; } = new ObservableCollection<TraySlot>();
+
+        public long Id { get; set; }
+
+        /// <summary>
+        /// 类型编号
+        /// </summary>
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 料盘类型 0:主盘  1:NG盘  2:OK盘
+        /// </summary>
+        public byte TrayType { get; set; } = 0;
+
+        /// <summary>
+        /// 料盘码
+        /// </summary>
+        public string SerialNo { get; set; }
+
+        /// <summary>
+        /// 产品码
+        /// </summary>
+        public string ProductNo { get; set; }
+
+        /// <summary>
+        /// 料盘参数对象
+        /// </summary>
+        public TrayConfig TrayConfiger { get; set; }
+
+        // 开始进料时刻
+        public long OnLoadMillis { get; set; }
+        // 开始准备时刻
+        public long OnReadyMillis { get; set; }
+        // 完全离开时刻
+        public long OnExitMillis { get; set; }
+
+        /// <summary>
+        /// X 方向的穴位坐标正负 1:+  -1:-
+        /// </summary>
+        public short DirectX { get; set; } = 1;
+
+        /// <summary>
+        /// X 方向的穴位坐标正负 1:+  -1:-
+        /// </summary>
+        public short DirectY { get; set; } = 1;
+
+        /// <summary>
+        /// 生成Matters的状态字符串
+        /// </summary>
+        /// <returns></returns>
+        public string ToMap()
+        {
+            StringBuilder builder = new StringBuilder();
+            foreach (var mat in Slots)
+            {
+                builder.Append(Convert.ToString(mat.Result));
+            }
+            return builder.ToString();
+        }
+
+        /// <summary>
+        /// 解析状态字符串并恢复到Matters集合中
+        /// </summary>
+        /// <param name="map"></param>
+        public void FromMap(string map, int rows, int cols)
+        {
+            //Matters.Clear();
+            //for (int i = 0; i < map.Length; i++)
+            //{
+            //    Matter matter = new Matter();
+            //    matter.TrayCols = cols;
+            //    matter.Set(i);
+            //    string s = Convert.ToString(map[i]);
+            //    matter.Result = Convert.ToByte(s);
+            //    Matters.Add(matter);
+            //}
+            //Num = map.Length;
+            Counts();
+        }
+
+        public int Row { get; set; }
+
+        public int Col { get; set; }
+
+        private int _num;
+
+        // 总数
+        public int Num
+        {
+            get { return _num; }
+            set { _num = value; RaisePropertyChanged(); }
+        }
+
+        private int _numOK;
+
+        public int NumOK
+        {
+            get { return _numOK; }
+            set
+            {
+                _numOK = value; RaisePropertyChanged();
+            }
+        }
+
+        private int _numNG;
+        // NG数
+        public int NumNG
+        {
+            get { return _numNG; }
+            set
+            {
+                _numNG = value; RaisePropertyChanged();
+            }
+        }
+
+        private int _unLocated;
+        // 剩余空位(未定位)
+        public int Unlocated
+        {
+            get { return _unLocated; }
+            set { _unLocated = value; RaisePropertyChanged(); }
+        }
+
+        public Tray(TrayConfig trayConfiger, byte trayType = 0, byte siteStatus = (byte)MatterStatus.NULL)
+        {
+            Id = BitConverter.ToInt64(System.Guid.NewGuid().ToByteArray(), 0);
+            TrayType = trayType;
+            Num = trayConfiger.Number;
+            Row = trayConfiger.Row;
+            Col = trayConfiger.Col;
+            if (trayConfiger == null)
+            {
+                throw new ArgumentException("tray model is null");
+            }
+            for (int i = 0; i < trayConfiger.Row; i++)
+            {
+                for (int j = 0; j < trayConfiger.Col; j++)
+                {
+                    TraySlot matter = new TraySlot
+                    {
+                        Index = j + i * trayConfiger.Col,
+                        ColX = j,
+                        RowY = i,
+                        IntervalX = trayConfiger.XPitch * j,
+                        IntervalY = trayConfiger.YPitch * i,
+                        Result = siteStatus
+                    };
+                    if (trayConfiger.SiteSwitchs != null && trayConfiger.SiteSwitchs.Count > matter.Index)
+                    {
+                        matter.IsEnable = trayConfiger.SiteSwitchs[matter.Index];
+                    }
+                    Slots.Add(matter);
+                }
+            }
+            Counts();
+        }
+
+        // 注入一个Slot数据
+        public void Inject(TraySlot slot)
+        {
+            //Matter thisMatter = Matters.Where(m => m.ColX == matter.ColX & m.RowY == matter.RowY).FirstOrDefault();
+            //if (thisMatter != null)
+            //{
+            //    if (matter.Code != null) thisMatter.Code = matter.Code;
+            //    if (matter.Result > 0) thisMatter.Result = matter.Result;
+            //    if (matter.ResultMsg != null) thisMatter.ResultMsg = matter.ResultMsg;
+            //    if (matter.FixsetX > 0) thisMatter.FixsetX = matter.FixsetX;
+            //    if (matter.FixsetY > 0) thisMatter.FixsetY = matter.FixsetY;
+            //    if (matter.FixsetR > 0) thisMatter.FixsetR = matter.FixsetR;
+            //}
+            foreach (var thisMat in Slots)
+            {
+                if (thisMat != null && thisMat.ColX == slot.ColX && thisMat.RowY == slot.RowY)
+                {
+                    thisMat.Code = slot.Code;
+                    thisMat.Result = slot.Result;
+                    thisMat.Located = slot.Located;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 注入一个Slot集合数据
+        /// </summary>
+        /// <param name="slots"></param>
+        public void Injects(List<TraySlot> slots)
+        {
+            foreach (var matter in slots)
+            {
+                Inject(matter);
+            }
+        }
+
+        public int Count(MatterStatus status)
+        {
+            int counter = 0;
+            foreach (var matter in Slots)
+            {
+                if (matter.Result == (byte)status) counter++;
+            }
+            return counter;
+        }
+
+        // 统计当前OK, NG数, 空位数
+        public void Counts()
+        {
+            _numNG = 0;
+            _numOK = 0;
+            _unLocated = 0;
+            foreach (var matter in Slots)
+            {
+                if (matter.Result == (byte)MatterStatus.OK) _numOK++;
+                if (matter.Result == (byte)MatterStatus.NG) _numNG++;
+                if (matter.Located == (byte)LocateStatus.TODO) _unLocated++;
+            }
+            NumOK = _numOK;
+            NumNG = _numNG;
+            Unlocated = _unLocated;
+        }
+
+        public void UpdateResults(MatterStatus status)
+        {
+            foreach (var matter in Slots)
+            {
+                matter.Result = (byte)status;
+            }
+            Counts();
+        }
+
+        public void Updates(Func<TraySlot, bool> cnd, Action<TraySlot> action)
+        {
+            foreach (var matter in Slots)
+            {
+                if (cnd.Invoke(matter)) action.Invoke(matter);
+            }
+        }
+
+        public List<TraySlot> FindSlots(MatterStatus status)
+        {
+            List<TraySlot> results = new List<TraySlot>();
+            foreach (var matter in Slots)
+            {
+                if (matter.Result == (byte)status) results.Add(matter);
+            }
+            return results;
+        }
+
+        public TraySlot FindNext(MatterStatus status)
+        {
+            foreach (var matter in Slots)
+            {
+                if (matter.Result == (byte)status) return matter;
+            }
+            return null;
+        }
+
+        public TraySlot FindNext(Func<TraySlot, bool> cnd)
+        {
+            foreach (var matter in Slots)
+            {
+                if (cnd.Invoke(matter)) return matter;
+            }
+            return null;
+        }
+
+        public void FindNext(ref TraySlot matter)
+        {
+            if (matter == null) return;
+            if (matter.Index == Slots.Count() - 1)
+            {
+                matter = null;
+                return;
+            }
+            matter = Slots[matter.Index + 1];
+        }
+
+        public override string ToString()
+        {
+            return $"id: {Id}, code: {SerialNo}";
+        }
+
+        public void PrintMatters()
+        {
+            log.Debug($"tray: {this}");
+            foreach (var matter in Slots)
+            {
+                log.Debug(matter.ToString());
+            }
+        }
+    }
+
+}

+ 172 - 0
SKMC.API/Device/Material/Tray/TrayConfig.cs

@@ -0,0 +1,172 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace SKMC.Api.Device.Material.Tray
+{
+    /// <summary>
+    /// 基础物料盘的配置模型
+    /// </summary>
+    public class TrayConfig : BindableBase
+    {
+
+        private long _id;
+
+        public long Id
+        {
+            get { return _id; }
+            set { _id = value; }
+        }
+
+        private int _length;
+
+        /// <summary>
+        /// Tray长度mm
+        /// </summary>
+        public int Length
+        {
+            get { return _length; }
+            set { _length = value; RaisePropertyChanged(); }
+        }
+
+
+        private int _width;
+
+        /// <summary>
+        /// Tray宽度mm
+        /// </summary>
+        public int Width
+        {
+            get { return _width; }
+            set { _width = value; RaisePropertyChanged(); }
+        }
+
+
+        private long _profileId;
+
+        /// <summary>
+        /// 档案Id
+        /// </summary>
+        public long ProfileId
+        {
+            get { return _profileId; }
+            set { _profileId = value; RaisePropertyChanged(); }
+        }
+
+        private int _row;
+
+        /// <summary>
+        /// Tray物料行数
+        /// </summary>
+        public int Row
+        {
+            get { return _row; }
+            set { _row = value; RaisePropertyChanged(); }
+        }
+
+        private int _col;
+
+        /// <summary>
+        /// Tray物料列数
+        /// </summary>
+        public int Col
+        {
+            get { return _col; }
+            set { _col = value; RaisePropertyChanged(); }
+        }
+
+        /// <summary>
+        /// Tray的最大物料数(行*列)
+        /// </summary>
+        public int Number { get => _row * _col; }
+
+        private double _xStart;
+
+        /// <summary>
+        /// Tray的首个物料位起始位置的X位置mm
+        /// </summary>
+        public double XStart
+        {
+            get { return _xStart; }
+            set { _xStart = value; RaisePropertyChanged(); }
+        }
+
+        private double _xPitch;
+
+        /// <summary>
+        /// Tray的X方向(列与列)物料间隔mm
+        /// </summary>
+        public double XPitch
+        {
+            get { return _xPitch; }
+            set { _xPitch = value; RaisePropertyChanged(); }
+        }
+
+        private double _yStart;
+
+        /// <summary>
+        /// Tray的首个物料位起始位置的Y位置mm
+        /// </summary>
+        public double YStart
+        {
+            get { return _yStart; }
+            set { _yStart = value; RaisePropertyChanged(); }
+        }
+
+        private double _yPitch;
+
+        /// <summary>
+        /// Tray的Y方向(行与行)物料间隔mm
+        /// </summary>
+        public double YPitch
+        {
+            get { return _yPitch; }
+            set { _yPitch = value; RaisePropertyChanged(); }
+        }
+
+        private double _height;
+
+        /// <summary>
+        /// Tray的高度
+        /// </summary>
+        public double Height
+        {
+            get { return _height; }
+            set { _height = value; RaisePropertyChanged(); }
+        }
+
+        public double XWidth => XStart + Col * XPitch;
+
+        public double YWidth => YStart + Row * YPitch;
+
+        private string _linePath;
+
+        /// <summary>
+        /// Tray的走线路径(物料格序号集字符串, 空格分隔)
+        /// </summary>
+        public string LinePath
+        {
+            get { return _linePath; }
+            set { _linePath = value; RaisePropertyChanged(); }
+        }
+
+        /// <summary>
+        /// Tray的走线路径(物料格序号集)
+        /// </summary>
+        public List<int> LinePathList
+        {
+            get 
+            {
+                string linePath = Regex.Replace(_linePath.Trim(), @"\s+", " ", RegexOptions.Multiline);
+                return linePath.Split(' ').Select(Int32.Parse).ToList();
+            }
+        }
+
+        /// <summary>
+        /// 物料格开关(用于跳过走线与作业)
+        /// </summary>
+        public List<bool> SiteSwitchs { get; set; } = new List<bool>();
+    }
+}

+ 146 - 0
SKMC.API/Device/Material/Tray/TraySlot.cs

@@ -0,0 +1,146 @@
+using Newtonsoft.Json;
+using Prism.Mvvm;
+using SKMC.Api.Recipe.Model;
+
+namespace SKMC.Api.Device.Material.Tray
+{
+    /// <summary>
+    /// 矩阵物料盘的物料/穴位模型
+    /// </summary>
+    [JsonObject(MemberSerialization.OptIn)]
+    public class TraySlot : BindableBase
+    {
+        [JsonProperty]
+        public int Index { get; set; }
+        [JsonProperty]
+        public int ColX { get; set; }
+        [JsonProperty]
+        public int RowY { get; set; }
+
+        // 是否可用
+        public bool IsEnable { get; set; } = true;
+
+        public int TrayCols { get; set; }
+
+        // X方向电机步距
+        public double PosX { get; set; }
+
+        // Y方向电机步距
+        public double PosY { get; set; }
+
+        // X方向间距 mm
+        public double IntervalX { get; set; }
+
+        // Y方向间距 mm
+        public double IntervalY { get; set; }
+
+        // X方向实际距离 mm
+        public double DistX { get; set; }
+
+        // Y方向实际距离 mm
+        public double DistY { get; set; }
+
+
+        private double _fixsetX;
+
+        // X方向的偏移量 mm(视觉辅助)
+        public double FixsetX
+        {
+            get { return _fixsetX; }
+            set { _fixsetX = value; }
+        }
+
+        private double _fixsetY;
+
+        // Y方向的偏移量 mm(视觉辅助)
+        public double FixsetY
+        {
+            get { return _fixsetY; }
+            set { _fixsetY = value; }
+        }
+
+        // R方向的偏移量 度(视觉辅助)
+        public double FixsetR { get; set; }
+
+        // 是否定位成功 (0:未定位  1:定位成功  2:定位失败)
+        public byte Located { get; set; }
+
+        private string _code;
+
+        // 物料二维码
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; RaisePropertyChanged(); }
+        }
+
+        private byte _result = (byte)MatterStatus.UNKNOWN;
+        [JsonProperty]
+        public byte Result
+        {
+            get { return _result; }
+            set { _result = value; RaisePropertyChanged(); }
+        }
+
+        // 详细结果
+        public string ResultMsg { get; set; }
+
+        // 重试次数
+        public int RetryTimes { get; set; }
+
+        public RecipePoint ProcessPoint { get; set; }
+
+        public void Set(int index)
+        {
+            Index = index;
+            RowY = index / TrayCols;
+            ColX = index % TrayCols;
+        }
+
+        /// <summary>
+        /// 获取产品或穴位定位的偏移量
+        /// </summary>
+        /// <param name="processTrayMatter"></param>
+        public void InjectFixset(TraySlot processTrayMatter)
+        {
+            FixsetX = processTrayMatter.FixsetX;
+            FixsetY = processTrayMatter.FixsetY;
+            if (ProcessPoint != null)
+            {
+                ProcessPoint.ProcessPointPositions[0].FixsetVal = processTrayMatter.FixsetX;
+                ProcessPoint.ProcessPointPositions[1].FixsetVal = processTrayMatter.FixsetY;
+            }
+        }
+
+        public override string ToString()
+        {
+            return $"index: {Index}, code: {Code}, result: {Result}, located: {Located}";
+        }
+    }
+
+    public enum TrayType : byte
+    {
+        Main = 0,
+        NG = 1,
+        OK = 2
+    }
+
+    public enum MatterStatus : byte
+    {
+        UNKNOWN = 0,
+        OK = 1,
+        NG = 2,
+        NGL = 3,
+        TODO = 4,
+        EXIST = 5,
+        NULL = 6,
+        ERROR = 9
+    }
+
+    public enum LocateStatus : byte
+    {
+        TODO = 0,
+        OK = 1,
+        FAIL = 2
+    }
+}

+ 35 - 0
SKMC.API/Loader.cs

@@ -0,0 +1,35 @@
+
+namespace SKMC.Api
+{
+    /// <summary>
+    /// 系统模块加载器
+    /// </summary>
+    public abstract class Loader
+    {
+        /// <summary>
+        /// 加载
+        /// </summary>
+        public virtual void Load() { }
+
+        /// <summary>
+        /// 启动
+        /// </summary>
+        public virtual void Start() { }
+
+        /// <summary>
+        /// 复位
+        /// </summary>
+        public virtual void Reset() { }
+
+        /// <summary>
+        /// 停止
+        /// </summary>
+        public virtual void Stop() { }
+
+        /// <summary>
+        /// 卸载
+        /// </summary>
+        public virtual void Unload() { }
+
+    }
+}

+ 111 - 0
SKMC.API/Motion/Config/MotionConfigStore.cs

@@ -0,0 +1,111 @@
+using SKMC.Api.Motion.Model;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Motion.Config
+{
+    /// <summary>
+    /// 运动控制配置库服务
+    /// </summary>
+    public abstract class MotionConfigStore
+    {
+        /// <summary>
+        /// 根据IO模块编号查询IO
+        /// </summary>
+        /// <param name="devNo"></param>
+        /// <param name="type"></param>
+        /// <returns></returns>
+        public abstract List<MotionIO> QueryIOByDev(int devNo, string type);
+
+        /// <summary>
+        /// 新增IO (未启用)
+        /// </summary>
+        /// <param name="motionIO"></param>
+        public abstract void InsertIO(MotionIO motionIO);
+
+        /// <summary>
+        /// 更新IO (未启用)
+        /// </summary>
+        /// <param name="motionIO"></param>
+        public abstract void UpdateIO(MotionIO motionIO);
+
+        /// <summary>
+        /// 删除IO (未启用)
+        /// </summary>
+        /// <param name="motionIO"></param>
+        public abstract void DeleteIO(MotionIO motionIO);
+
+        /// <summary>
+        /// 查询所有AO
+        /// </summary>
+        /// <param name="type"></param>
+        /// <returns></returns>
+        public abstract List<MotionAO> QueryAos(string type);
+
+        /// <summary>
+        /// 新增AO (未启用)
+        /// </summary>
+        /// <param name="motionAO"></param>
+        public abstract void InsertAO(MotionAO motionAO);
+
+        /// <summary>
+        /// 更新AO
+        /// </summary>
+        /// <param name="motionAO"></param>
+        public abstract void UpdateAO(MotionAO motionAO);
+
+        /// <summary>
+        /// 删除AO (未启用)
+        /// </summary>
+        /// <param name="motionAO"></param>
+        public abstract void DeleteAO(MotionAO motionAO);
+
+        /// <summary>
+        /// 根据轴号获取单个电机轴对象
+        /// </summary>
+        /// <param name="axisNo"></param>
+        /// <returns></returns>
+        public abstract MotionAxis FetchAxis(short axisNo);
+
+        /// <summary>
+        /// 查询所有的电机轴对象
+        /// </summary>
+        /// <returns></returns>
+        public abstract List<MotionAxis> QueryAxises();
+
+        /// <summary>
+        /// 新增电机轴 (未启用)
+        /// </summary>
+        /// <param name="motionAxis"></param>
+        public abstract void InsertAxis(MotionAxis motionAxis);
+
+        /// <summary>
+        /// 更新电机轴
+        /// </summary>
+        /// <param name="motionAxis"></param>
+        public abstract void UpdateAxis(MotionAxis motionAxis);
+
+        /// <summary>
+        /// 删除电机轴 (未启用)
+        /// </summary>
+        /// <param name="motionAxis"></param>
+        public abstract void DeleteAxis(MotionAxis motionAxis);
+
+        /// <summary>
+        /// 查询SDO对象集
+        /// </summary>
+        /// <returns></returns>
+        public abstract List<MotionSdo> QuerySdoes();
+
+        /// <summary>
+        /// 查询PDO(读取/写入)对象集
+        /// </summary>
+        /// <param name="rw">0:读取型, 1:写入型</param>
+        /// <returns></returns>
+        public abstract List<MotionPdo> QueryPdoes(short rw = 0);
+    }
+}

+ 52 - 0
SKMC.API/Motion/Config/MotionConstants.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Motion.Config
+{
+    public class MotionConstants
+    {
+        /// <summary>
+        /// 正在回零中
+        /// </summary>
+        public const short HOME_IN_PROGRESS = 0;
+
+        /// <summary>
+        /// 回零中断或者没有开始启动
+        /// </summary>
+        public const short HOME_INTERRUPTED = 1;
+
+        /// <summary>
+        /// 回零结束, 但没有到设定的目标位置
+        /// </summary>
+        public const short HOME_NOT_REACH = 2;
+
+        /// <summary>
+        /// 回零成功
+        /// </summary>
+        public const short HOME_SUCESS = 3;
+
+        /// <summary>
+        /// 回零中发生错误, 同时速度不为0
+        /// </summary>
+        public const short HOME_ERR_MOVE = 4;
+
+        /// <summary>
+        /// 回零中发生错误, 同时速度为0
+        /// </summary>
+        public const short HOME_ERR_STOP = 5;
+
+        /// <summary>
+        /// 电机类型为闭环
+        /// </summary>
+        public const string AXIS_TYPE_CLOSE = "闭环";
+
+        /// <summary>
+        /// 电机类型为开环
+        /// </summary>
+        public const string AXIS_TYPE_OPEN = "开环";
+
+    }
+}

+ 70 - 0
SKMC.API/Motion/Config/MotionException.cs

@@ -0,0 +1,70 @@
+using SKMC.Api.Common.Exceptions;
+
+namespace SKMC.Api.Motion.Config
+{
+    /// <summary>
+    /// 运动控制异常
+    /// </summary>
+    public class MotionException : ExceptionBase
+    {
+        /// <summary>
+        /// 控制卡初始化失败 8
+        /// </summary>
+        public static int CARD_INIT = 0b_0000_0000_1000;
+
+        /// <summary>
+        /// 电机或驱动器报警 384
+        /// </summary>
+        public static int Axis_Alarm = 0b_0001_1000_0000;
+
+        /// <summary>
+        /// 电机未上使能 385
+        /// </summary>
+        public static int Axis_Enable = 0b_0001_1000_0001;
+
+        /// <summary>
+        /// 电机触发正限位 386
+        /// </summary>
+        public static int Axis_PEL = 0b_0001_1000_0010;
+
+        /// <summary>
+        /// 电机触发负限位 387
+        /// </summary>
+        public static int Axis_NEL = 0b_0001_1000_0011;
+
+        /// <summary>
+        /// 电机未回零 388
+        /// </summary>
+        public static int Axis_NORG = 0b_0001_1000_0100;
+
+        /// <summary>
+        /// 电机未通过动作验证 392
+        /// </summary>
+        public static int Axis_Checkfail = 0b_0001_1000_1000;
+
+        /// <summary>
+        /// 电机到位超时 393
+        /// </summary>
+        public static int Axis_Timeout_Move = 0b_0001_1000_1001;
+
+        /// <summary>
+        /// 电机回零超时 396
+        /// </summary>
+        public static int Axis_Timeout_Home = 0b_0001_1000_1100;
+
+        /// <summary>
+        /// 气缸未通过动作验证 460
+        /// </summary>
+        public static int Cylinde_Checkefail = 0b_0001_1100_1100;
+
+        /// <summary>
+        /// 电机未通过安全条件 416
+        /// </summary>
+        public static int Axis_Unsafe = 0b_0001_1010_0000;
+
+        /// <summary>
+        /// 气缸未通过安全条件 480
+        /// </summary>
+        public static int Cylinde_Unsafe = 0b_0001_1110_0000;
+    }
+}

+ 57 - 0
SKMC.API/Motion/Control/IMotionChecker.cs

@@ -0,0 +1,57 @@
+using SKMC.Api.Process.Model;
+using System;
+using System.Collections.Generic;
+
+namespace SKMC.Api.Motion.Control
+{
+    /// <summary>
+    /// 运动状态验证器接口
+    /// 适用于对动作后的状态/数值验证
+    /// </summary>
+    public interface IMotionChecker
+    {
+        /// <summary>
+        /// 设置条件集合
+        /// </summary>
+        /// <param name="diCnds"></param>
+        /// <param name="axisCnds"></param>
+        void SetCnds(List<DioCnd> diCnds = null, List<AxisCnd> axisCnds = null);
+
+        /// <summary>
+        /// 设置条件集合
+        /// </summary>
+        /// <param name="diCnds"></param>
+        void SetCnds(List<DioCnd> diCnds);
+
+        /// <summary>
+        /// 设置条件集合
+        /// </summary>
+        /// <param name="axisCnds"></param>
+        void SetCnds(List<AxisCnd> axisCnds);
+
+        /// <summary>
+        /// 设置条件
+        /// </summary>
+        /// <param name="diCnd"></param>
+        /// <param name="axisCnd"></param>
+        void SetCnd(DioCnd diCnd = null, AxisCnd axisCnd = null);
+
+        /// <summary>
+        /// 状态检测高速模式 (数据直读)
+        /// </summary>
+        /// <param name="checkCnd"></param>
+        /// <param name="onSuccess"></param>
+        /// <param name="onTimeout"></param>
+        /// <param name="timeout"></param>
+        void CheckStatusFastMode(Func<bool> checkCnd, Action onSuccess = null, Action onTimeout = null, int timeout = 5000);
+
+        /// <summary>
+        /// 状态检测低速模式 (使用缓存数据有少量延迟)
+        /// </summary>
+        /// <param name="checkCnd"></param>
+        /// <param name="onSuccess"></param>
+        /// <param name="onTimeout"></param>
+        /// <param name="timeout"></param>
+        void CheckStatusLowMode(Func<bool> checkCnd, Action onSuccess = null, Action onTimeout = null, int timeout = 5000);
+    }
+}

+ 624 - 0
SKMC.API/Motion/Control/IMotionControl.cs

@@ -0,0 +1,624 @@
+using SKMC.Api.Motion.Model;
+using SKMC.Api.Recipe.Model;
+using System;
+using System.Collections.Generic;
+
+namespace SKMC.Api.Motion.Control
+{
+    /// <summary>
+    /// 基于流程化的运动控制层接口
+    /// 结合点位、速度配置, 方便控制脚本调用
+    /// </summary>
+    public interface IMotionControl
+    {
+        /// <summary>
+        /// 设置参数
+        /// </summary>
+        /// <param name="key">interval/timeout_move/timeout_home</param>
+        /// <param name="value"></param>
+        void Set(string key, int value);
+
+        /// <summary>
+        /// 获取参数
+        /// </summary>
+        /// <param name="key">interval/timeout_move/timeout_home</param>
+        /// <returns></returns>
+        int Get(string key);
+
+        /// <summary>
+        /// 控制使能, true: motion控制有效,  false: motion控制无效
+        /// 运动过程中如有电机抛出MotionException, 该控制使能自动失效
+        /// </summary>
+        void Enable(bool enabled = true);
+
+        /// <summary>
+        /// 控制使能是否有效
+        /// </summary>
+        /// <returns></returns>
+        bool IsEnable();
+
+        /// <summary>
+        /// 开启所有电机使能
+        /// </summary>
+        void EnableAllAxises();
+
+        /// <summary>
+        /// 关闭所有电机使能
+        /// </summary>
+        void DisableAllAxises();
+
+        /// <summary>
+        /// 判断控制卡是否连接状态
+        /// </summary>
+        /// <returns></returns>
+        bool IsCardConnected();
+
+        /// <summary>
+        /// 电机上使能
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="clearError">清除驱动报警</param>
+        void EnableAxis(string axisCode, bool clearError = true);
+
+        /// <summary>
+        /// 电机下使能
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        void DisableAxis(string axisCode);
+
+        /// <summary>
+        /// 多个电机上使能
+        /// </summary>
+        /// <param name="axisCodes">电机码集合</param>
+        /// <param name="clearError">清除驱动报警</param>
+        void EnableAxises(List<string> axisCodes, bool clearError = true);
+
+        /// <summary>
+        /// 多个电机下使能
+        /// </summary>
+        /// <param name="axisCodes">电机码集合</param>
+        void DisableAxises(List<string> axisCodes);
+
+        /// <summary>
+        /// 获取电机对象
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <returns>电机对象</returns>
+        MotionAxis GetAxis(string axisCode);
+
+        /// <summary>
+        /// 指定轴回零
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="waiting">是否等待(!注意:目前此参数设置为false无效)</param>
+        /// <param name="safeCnd">安全条件, 回零中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess"></param>
+        /// <param name="onTimeout"></param>
+        /// <param name="timeout">超时时间(毫秒)</param>
+        void HomeAxis(string axisCode, bool waiting = true, Func<bool> safeCnd = null, Action onSuccess = null, Action onTimeout = null, int timeout = 60000);
+
+        /// <summary>
+        /// 指定轴快速回零, 该轴必须是闭环电机, 如果是开环电机则降级为普通的HomeAxis回零
+        /// 并且传入的stateCheck为true后(表示总线连接OK并且上一次回零到现在未中断),使用speedCode速度移动到0位置再进行回零。
+        /// 如果传入的stateCheck为false则降级为普通的HomeAxis回零
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">快速移动到0位置的速度码, !注意:为确保安全设置合适的速度</param>
+        /// <param name="stateCheck">状态判断, 这里指Ecat总线连接状态以及上一次回零后Ecat总线是否正常</param>
+        /// <param name="waiting">是否等待(!注意:目前此参数设置为false无效)</param>
+        /// <param name="safeCnd">安全条件, 回零中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess"></param>
+        /// <param name="onTimeout"></param>
+        /// <param name="timeout">超时时间(毫秒)</param>
+        void HomeAxisFast(string axisCode, string speedCode, bool stateCheck, bool waiting = true,
+            Func<bool> safeCnd = null, Action onSuccess = null, Action onTimeout = null, int timeout = 60000);
+
+        /// <summary>
+        /// 指定轴反向回零.
+        /// 在无法进行负方向回零时, 可通过该方法往正方向回零, 回零完毕后设置当前位置为正限位最大值, 即可实现与负方向回零相同效果
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="homeMode">回零模式, 例如正限位+Z相回零的模式是2</param>
+        /// <param name="maxPos">限位位置相对原点的位置, 单位mm, 推荐在限位位置设置点位并从点位获取</param>
+        /// <param name="waiting">是否等待</param>
+        /// <param name="safeCnd">安全条件, 回零中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess"></param>
+        /// <param name="onTimeout"></param>
+        /// <param name="timeout">超时时间(毫秒)</param>
+        void HomeAxisReverse(string axisCode, short homeMode, double maxPos, bool waiting = true,
+            Func<bool> safeCnd = null, Action onSuccess = null, Action onTimeout = null, int timeout = 60000);
+
+        /// <summary>
+        /// 判断绑定某个点位的电机(一个或多个)是否在该点位附近。需同时满足negativeOffset与PositiveOffset的位置限定后返回true.
+        /// <para>具体判断如下:</para>
+        /// <para>1.电机未上使能, 返回false</para>
+        /// <para>2.电机Enc数值 小于 点位设置值 - negativeOffset 或者 电机Enc数值 大于 点位设置值 + positiveOffset, 返回false</para>
+        /// <para>3.剩下返回true</para>
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        /// <param name="axisIndex">绑定点位的电机序号, 0开头, 默认-1表示该点位的所有电机</param>
+        /// <param name="negativeOffset">大于该点位的负方向距离</param>
+        /// <param name="positiveOffset">小于该点位的正方向距离</param>
+        /// <param name="onPassed">满足条件后的动作</param>
+        /// <param name="onFailed">未满足条件后的动作</param>
+        /// <returns>是否满足位置条件</returns>
+        bool CheckAxisNearPoint(string pointCode, short axisIndex = -1, double negativeOffset = 0.1, double positiveOffset = 0.1);
+
+        /// <summary>
+        /// 通过点位码驱动对应的Axis运动, speedRadio表示每秒旋转圈数
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        /// <param name="speedRadio">每秒旋转圈数</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        /// <param name="checkCnd">检测的条件</param>
+        /// <param name="onChecked">满足检测条件的动作</param>
+        void MoveAxisRound(string pointCode, double speedRadio,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 让绑定点位码的(多个)电机以指定速度运动到点位码
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        /// <param name="speedCode">速度码, null表示使用该点配置的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="sync">如果2个轴同时移动,是否需要同时达到(直线轨迹)</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void MoveAxisPoint(string pointCode, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null, bool sync = false);
+
+        /// <summary>
+        /// 让绑定点位码的(多个)电机以指定速度运动到点位码
+        /// </summary>
+        /// <param name="processPoint">点位对象</param>
+        /// <param name="speedCode">速度码, null表示使用该点配置的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="sync">如果2个轴同时移动,是否需要同时达到(直线轨迹)</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void MoveAxisPoint(RecipePoint processPoint, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null,
+            bool sync = false);
+
+        /// <summary>
+        /// 让绑定点位码的(多个)电机以指定速度运动到点位码并能叠加指定偏移量
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        /// <param name="posOffsets">各个电机的偏移量数组, 需要按电机绑定顺序赋值</param>
+        /// <param name="speedCode">速度码, null表示使用该点配置的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="distanceUnit">是否距离单位, true:毫米, false:电机步距</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="sync">如果2个轴同时移动,是否需要同时达到(直线轨迹)</param>
+        /// <param name="safeCnd">安全条件, 运动中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        /// <param name="checkCnd">检测的条件</param>
+        /// <param name="onChecked">满足检测条件的动作</param>
+        void MoveAxisPointFixset(string pointCode, double[] posOffsets, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null,
+            bool sync = false);
+
+        /// <summary>
+        /// 让绑定点位码的(多个)电机以指定速度运动到点位码并能叠加指定偏移量
+        /// </summary>
+        /// <param name="processPoint">点位对象</param>
+        /// <param name="posOffsets">各个电机的偏移量数组, 需要按电机绑定顺序赋值</param>
+        /// <param name="speedCode">速度码, null表示使用该点配置的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="sync">如果2个轴同时移动,是否需要同时达到(直线轨迹)</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void MoveAxisPointFixset(RecipePoint processPoint, double[] posOffsets, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null,
+            bool sync = false);
+
+        /// <summary>
+        /// 让绑定点位码的指定单个电机以指定速度运动到点位码
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码, null表示使用该点配置的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void MoveAxisPointOne(string pointCode, string axisCode, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+
+        void MoveAxisPointOneFixset(string pointCode, string axisCode, double fixset, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 让绑定点位码的指定单个电机以指定速度运动到点位码
+        /// </summary>
+        /// <param name="processPoint">点位对象</param>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码, null表示使用该点配置的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void MoveAxisPointOne(RecipePoint processPoint, string axisCode, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 聚合多个点位码并同时运动, 注意: 每个点位的运动速度来自各自的点位(ProcessPoint)中的速度码
+        /// </summary>
+        /// <param name="processPoints">需要聚合运动的点位对象集合</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void MoveAxisAssemblePoints(List<RecipePoint> processPoints,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 聚合多个点位码并同时运动, 注意: 每个点位的运动速度优先使用speedCode, 如果speedCode为空则使用各自的点位中设置的速度码
+        /// </summary>
+        /// <param name="pointCodes">需要聚合运动的点位码集合</param>
+        /// <param name="speedCode">速度码, null表示使用各点位的默认速度码</param>
+        /// <param name="waiting">是否等待到位, 默认为true</param>
+        /// <param name="distanceUnit">是否距离单位, true:毫米, false:电机步距</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="safeCnd">安全条件, 运动中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        /// <param name="checkCnd">检测的条件</param>
+        /// <param name="onChecked">满足检测条件的动作</param>
+        void MoveAxisAssemblePoints(string[] pointCodes, string speedCode = null,
+            bool waiting = true, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 等待电机运动到位
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="timeout">超时时间</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="safeCnd">安全条件, 运动中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        /// <param name="checkCnd">检测的条件</param>
+        /// <param name="onChecked">满足检测条件的动作</param>
+        void WaitAxisDone(string axisCode, int timeout = 10000, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 等待电机运动到位
+        /// </summary>
+        /// <param name="motionAxis">电机对象</param>
+        /// <param name="timeout">超时时间</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void WaitAxisDone(MotionAxis motionAxis, int timeout = 10000, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 等待点位上所有电机到位
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        /// <param name="timeout">超时时间</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void WaitAxisPointDone(string pointCode, int timeout = 10000, double inRange = -1,
+            Action onSuccess = null, Action onTimeout = null);
+
+        /// <summary>
+        /// 等待点位上所有电机到位
+        /// </summary>
+        /// <param name="processPoint">点位对象</param>
+        /// <param name="timeout">超时时间</param>
+        /// <param name="inRange">可偏移的范围, 单位毫米或者步距</param>
+        /// <param name="safeCnd">安全条件, 运动中如果不满足该安全条件立即停止并报警</param>
+        /// <param name="onSuccess">运动完成后的回调动作</param>
+        /// <param name="onTimeout">超时未完成的回调动作</param>
+        void WaitAxisPointDone(RecipePoint processPoint, int timeout = 10000, double inRange = -1,
+            Func<bool> safeCnd = null, Action onSuccess = null, Action onTimeout = null,
+            Func<bool> checkCnd = null, Action onChecked = null);
+
+        /// <summary>
+        /// 等待电机组所有电机到位
+        /// </summary>
+        /// <param name="axisCodes">电机码集合</param>
+        /// <param name="timeout">超时时间</param>
+        void WaitAxisGroupDone(List<string> axisCodes, int timeout = 10000);
+
+        /// <summary>
+        /// 等待并阻塞当前线程, 直到满足特定条件
+        /// </summary>
+        /// <param name="cndDone">完成条件 (满足后释放阻塞)</param>
+        /// <param name="onTimeout">超时动作</param>
+        /// <param name="timeout">超时时间(ms)</param>
+        /// <param name="interval">监测条件的间隔时间(ms)</param>
+        void WaitCndDone(Func<bool> cndDone, Action onTimeout, int timeout, int interval = 25);
+
+        /// <summary>
+        /// 电机按一定速度持续运动, 非阻塞
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码</param>
+        /// <param name="direction">方向</param>
+        void MoveKeepAxis(string axisCode, string speedCode, ushort direction);
+
+        /// <summary>
+        /// 电机相对运动
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码</param>
+        /// <param name="distance">距离</param>
+        /// <param name="waiting">是否等待</param>
+        void MoveAxisRel(string axisCode, string speedCode, double distance, bool waiting = true);
+
+
+        /// <summary>
+        /// 电机绝对运动,确保回零后调用
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码</param>
+        /// <param name="position">坐标</param>
+        /// <param name="waiting">是否等待</param>
+        void MoveAxisAbs(string axisCode, string speedCode, double position, bool waiting = false);
+
+        /// <summary>
+        /// 电机绝对运动,确保回零后调用
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="processSpeed">速度对象</param>
+        /// <param name="position">坐标</param>
+        /// <param name="waiting">是否等待</param>
+        void MoveAxisAbs(string axisCode, RecipeSpeed processSpeed, double position, bool waiting = false);
+
+
+        /// <summary>
+        /// 停止单个电机
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        void StopAxis(string axisCode, bool waiting = false);
+
+        /// <summary>
+        /// 停止多个电机
+        /// </summary>
+        /// <param name="axisCodes">电机码集合</param>
+        void StopAxises(List<string> axisCodes);
+
+        /// <summary>
+        /// 点位上多个电机停止运动
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        void StopAxisPoint(string pointCode);
+
+        /// <summary>
+        /// 点位上多个电机急停
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        void StopAxisPointEmg(string pointCode);
+
+        /// <summary>
+        /// 多个电机急停
+        /// </summary>
+        /// <param name="axisCodes">电机码集合</param>
+        void StopAxisesEmg(List<string> axisCodes);
+
+        /// <summary>
+        /// 所有电机停止
+        /// </summary>
+        void StopAxisAll();
+
+        /// <summary>
+        /// 暂停一个电机运动
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        void PauseAxis(string axisCode);
+
+        /// <summary>
+        /// 暂停一个点位上多个电机运动
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        void PauseAxisPoint(string pointCode);
+
+        /// <summary>
+        /// 恢复一个电机运动
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        void ResumeAxis(string axisCode);
+
+        /// <summary>
+        /// 暂停一个点位上多个电机运行
+        /// </summary>
+        /// <param name="pointCode">点位码</param>
+        void ResumeAxisPoint(string pointCode);
+
+        /// <summary>
+        /// 清除电机计数(反馈值与目标值)
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        void ClearAxisCounter(string axisCode);
+
+        /// <summary>
+        /// 清除电机驱动器报警(部分驱动器可能无效)
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        void ClearAxisError(string axisCode);
+
+        /// <summary>
+        /// 更新电机持续运行时的速度
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码</param>
+        void UpdateAxisKeepSpeed(string axisCode, string speedCode);
+
+        /// <summary>
+        /// 更新电机持续运行时的速度.
+        /// [注意]速度单位是mm/s, 慎用
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="recipeSpeed">速度对象, 至少需要MaxVel、Acc、Dec 这3个属性</param>
+        void UpdateAxisKeepSpeed(string axisCode, RecipeSpeed recipeSpeed);
+
+        /// <summary>
+        /// 更新电机点对点移动时的速度
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="speedCode">速度码</param>
+        void UpdateAxisMoveSpeed(string axisCode, string speedCode);
+
+        /// <summary>
+        /// 更新电机点对点移动时的速度.
+        /// [注意]速度单位是mm/s, 慎用
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="recipeSpeed">速度对象, 至少需要MaxVel、Acc、Dec 这3个属性</param>
+        void UpdateAxisMoveSpeed(string axisCode, RecipeSpeed recipeSpeed);
+
+        /// <summary>
+        /// 开始监测电机步距
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <param name="ticks">时间周期, -1表示当前</param>
+        void StartAxisWatch(string axisCode, long ticks = -1);
+
+        /// <summary>
+        /// 读取电机状态并获取对象
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <returns>电机对象</returns>
+        MotionAxis ReadAxisStatus(string axisCode);
+
+        /// <summary>
+        /// 读取电机状态
+        /// </summary>
+        /// <param name="motionAxis">电机对象</param>
+        void ReadAxisStatus(MotionAxis motionAxis);
+
+        /// <summary>
+        /// 电机到位判断
+        /// </summary>
+        /// <param name="axisCode">电机码</param>
+        /// <returns>是否到位</returns>
+        bool AxisDoneCheck(string axisCode);
+
+        /// <summary>
+        /// 多个电机到位判断, 如果其中一个电机未到位返回false
+        /// </summary>
+        /// <param name="axisCodes">电机码</param>
+        /// <returns>是否到位</returns>
+        bool AxisDoneCheck(List<string> axisCodes);
+
+
+        /// <summary>
+        /// 设置DO点开关值(会检测接口是否失效, 推荐气缸等动作IO使用)
+        /// </summary>
+        /// <param name="doCode">DO码</param>
+        /// <param name="value">开关值</param>
+        /// <param name="logged">本次动作是否记录到日志</param>
+        void PushIO(string doCode, short value, bool logged = true);
+
+        /// <summary>
+        /// 设置DO点开关值
+        /// </summary>
+        /// <param name="doCode">DO码</param>
+        /// <param name="value">开关值</param>
+        /// <param name="logged">本次动作是否记录到日志</param>
+        void PushIO(string doCode, int value, bool logged = true);
+
+        /// <summary>
+        /// 设置DO点开关值(不检测接口是否失效, 推荐指示灯等非动作IO使用)
+        /// </summary>
+        /// <param name="doCode">DO码</param>
+        /// <param name="value">开关值</param>
+        /// <param name="logged">本次动作是否记录到日志</param>
+        void SetIO(string doCode, short value, bool logged = true);
+
+        /// <summary>
+        /// 设置DO点开关值反向
+        /// </summary>
+        /// <param name="doCode">DO码</param>
+        void PushIORev(string doCode);
+
+        /// <summary>
+        /// 设置一对DO点开关值
+        /// </summary>
+        /// <param name="do1Code">DO1码</param>
+        /// <param name="do1Value">DO1开关值</param>
+        /// <param name="do2Code">DO2码</param>
+        /// <param name="do2Value">DO2开关值</param>
+        void PushIOPair(string do1Code, short do1Value, string do2Code, short do2Value);
+
+        /// <summary>
+        /// 获取DI开关值
+        /// </summary>
+        /// <param name="diCode">DI码</param>
+        /// <param name="fromCache">是否从缓存获取</param>
+        /// <returns></returns>
+        short GetDiValue(string diCode, bool fromCache = true);
+
+        /// <summary>
+        /// 读取一次DI开关值并写入cache, 同时返回该数值
+        /// </summary>
+        /// <param name="diCode">DI码</param>
+        /// <returns></returns>
+        short ReadDiValue(string diCode);
+
+        /// <summary>
+        /// 获取DO开关值
+        /// </summary>
+        /// <param name="doCode">DO码</param>
+        /// <param name="fromCache">是否从缓存获取</param>
+        /// <returns></returns>
+        short GetDoValue(string doCode, bool fromCache = true);
+
+        /// <summary>
+        /// 读取一次DO开关值并写入cache, 同时返回该数值
+        /// </summary>
+        /// <param name="diCode">DI码</param>
+        /// <returns></returns>
+        short ReadDoValue(string doCode);
+
+        /// <summary>
+        /// 获取模拟量数值
+        /// </summary>
+        /// <param name="adCode">AD码</param>
+        /// <returns></returns>
+        short GetAdValue(string adCode);
+
+        /// <summary>
+        /// 获取模拟量数值
+        /// </summary>
+        /// <param name="adIndex">AD序号</param>
+        /// <returns></returns>
+        short GetAdValue(short adIndex);
+
+        /// <summary>
+        /// 读取一次模拟量数值到MotionCacher的MotionAO中
+        /// </summary>
+        /// <param name="adCode">AD码</param>
+        void ReadAdValue(string adCode);
+
+        /// <summary>
+        /// 读取一次模拟量数值到MotionCacher的MotionAO中
+        /// </summary>
+        /// <param name="adIndex">AD序号</param>
+        void ReadAdValue(short adIndex);
+
+    }
+
+}

+ 384 - 0
SKMC.API/Motion/Driver/IMotionDriver.cs

@@ -0,0 +1,384 @@
+
+using SKMC.Api.Motion.Model;
+using System.Text;
+
+namespace SKMC.Api.Motion.Driver
+{
+    /// <summary>
+    /// 运动控制卡基础控制接口
+    /// </summary>
+    public interface IMotionDriver
+    {
+        /// <summary>
+        /// 运动控制数据解析器接口
+        /// </summary>
+        IMotionDriverParser MotionParser { get; set; }
+
+        /// <summary>
+        /// 获取型号
+        /// </summary>
+        /// <returns></returns>
+        string GetModelCode();
+
+        /// <summary>
+        /// 初始化控制卡
+        /// </summary>
+        void InitBoard();
+
+        /// <summary>
+        /// 获取硬件句柄
+        /// </summary>
+        /// <returns></returns>
+        ulong GetHandler();
+
+        /// <summary>
+        /// 关闭控制卡
+        /// </summary>
+        void CloseBoard();
+
+        /// <summary>
+        /// 检测控制卡状态
+        /// </summary>
+        /// <returns></returns>
+        bool CheckHealth();
+
+        /// <summary>
+        /// 复位控制卡
+        /// </summary>
+        void ResetBoard();
+
+        /// <summary>
+        /// 获取控制卡Ethercat总线状态
+        /// </summary>
+        /// <returns>0为正常, 其他为异常</returns>
+        int GetEcatStatus();
+
+        /// <summary>
+        /// 获取控制卡Ethercat总线错误码
+        /// </summary>
+        /// <returns>0为正常, 其他为错误码</returns>
+        int GetEcatErrorCode();
+
+        /// <summary>
+        /// 获取Ethercat总线上的电机数量
+        /// </summary>
+        /// <returns>电机数量</returns>
+        int GetAxisNum();
+
+        /// <summary>
+        /// 获取Ethercat总线上的IO输入数量
+        /// </summary>
+        /// <returns>IO输入数量</returns>
+        int GetDiNum();
+
+        /// <summary>
+        /// 获取Ethercat总线上的IO输出数量
+        /// </summary>
+        /// <returns>IO输出数量</returns>
+        int GetDoNum();
+
+        /// <summary>
+        /// 获取Ethercat总线上的AD输入数量
+        /// </summary>
+        /// <returns>AD输入数量</returns>
+        int GetAdNum();
+
+        /// <summary>
+        /// 获取Ethercat总线上的DA输出数量
+        /// </summary>
+        /// <returns>DA输出数量</returns>
+        int GetDaNum();
+
+        /// <summary>
+        /// 电机是否使能状态
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>是否使能状态</returns>
+        bool IsAxisEnable(short axisNo);
+
+        /// <summary>
+        /// 获取电机状态值
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="status">状态数值</param>
+        void GetAxisStatus(short axisNo, ref int status);
+
+        /// <summary>
+        /// 获取多个电机状态值
+        /// </summary>
+        /// <param name="count"></param>
+        /// <param name="statusArr"></param>
+        void GetMultiAxisStatus(short count, ref int[] statusArr);
+
+        /// <summary>
+        /// 电机上使能
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        void AxisEnable(short axisNo);
+
+        /// <summary>
+        /// 电机下使能
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        void AxisDisable(short axisNo);
+
+        /// <summary>
+        /// 电机上使能或下使能
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="enabled">true为上使能, false为下使能</param>
+        void AxisEnabled(short axisNo, bool enabled);
+
+        /// <summary>
+        /// 设置运动参数(轴状态为到位isDone时生效)
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="moveParam">运动参数</param>
+        void SetMoveProfile(short axisNo, AxisMoveParma moveParam);
+
+        /// <summary>
+        /// 更新绝对运动/相对运动时运动参数(轴状态为忙碌isBusy时生效)
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="moveParam">运动参数</param>
+        void UpdateMoveTargetProfile(short axisNo, AxisMoveParma moveParam);
+
+        /// <summary>
+        /// 更新持续运动/Jog运动时运动参数(轴状态为忙碌isBusy时生效)
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="moveParam">运动参数</param>
+        void UpdateMoveKeepProfile(short axisNo, AxisMoveParma moveParam);
+
+        /// <summary>
+        /// 相对运动
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="direction">运动方向</param>
+        /// <param name="dist">运行步距</param>
+        /// <returns>命令返回码</returns>
+        short MoveRel(short axisNo, ushort direction, double dist);
+
+        /// <summary>
+        /// 绝对运动
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="direction">运动方向</param>
+        /// <param name="dist">运行步距</param>
+        /// <returns>命令返回码</returns>
+        short MoveAbs(short axisNo, ushort direction, double dist);
+
+        /// <summary>
+        /// 中途更换目标位置
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="direction"></param>
+        /// <param name="dist"></param>
+        /// <returns>命令返回码</returns>
+        short MoveAbsUpdate(short axisNo, ushort direction, double dist);
+
+        /// <summary>
+        /// 持续运动
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="vel"></param>
+        /// <param name="direction"></param>
+        /// <returns>命令返回码</returns>
+        short MoveKeep(short axisNo, double vel, ushort direction);
+
+        /// <summary>
+        /// 停止运动(减速停止)
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>命令返回码</returns>
+        short MoveStop(short axisNo);
+
+        /// <summary>
+        /// 暂停运动
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>命令返回码</returns>
+        short MovePause(short axisNo);
+
+        /// <summary>
+        /// 恢复运动
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>命令返回码</returns>
+        short MoveResume(short axisNo);
+
+        /// <summary>
+        /// 回零运动
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="homeParam"></param>
+        /// <returns>命令返回码</returns>
+        short Home(short axisNo, AxisHomeParam homeParam);
+
+        /// <summary>
+        /// 获取回零状态
+        /// </summary>
+        /// <param name="axisNo"></param>
+        /// <returns>0: 回零成功,  1: 回零中,  2: 回零异常</returns>
+        short GetHomeStatus(short axisNo);
+
+        /// <summary>
+        /// 退出回零运动
+        /// </summary>
+        /// <param name="axisNo"></param>
+        /// <returns></returns>
+        short EndHome(short axisNo);
+
+        /// <summary>
+        /// 获取编码器反馈值
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>反馈值</returns>
+        double GetEncPos(short axisNo);
+
+        /// <summary>
+        /// 获取运动目标值
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>目标值</returns>
+        double GetPrfPos(short axisNo);
+
+        /// <summary>
+        /// 设置当前编码器位置(慎用)
+        /// 注意: 调用该方法时需要确认电机上使能并且是静止状态
+        /// 绝对值编码器不推荐使用
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <param name="encPos">编码器位置</param>
+        /// <returns></returns>
+        short SetEncPos(short axisNo, double encPos);
+
+        /// <summary>
+        /// 急停
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        /// <returns>命令返回码</returns>
+        short EmgStop(short axisNo);
+
+        /// <summary>
+        /// 清除反馈值及目标值
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        void ClearCounter(short axisNo);
+
+        /// <summary>
+        /// 清除报错(是否有效取决于具体错误)
+        /// </summary>
+        /// <param name="axisNo">电机轴号</param>
+        void ClearError(short axisNo);
+
+        /// <summary>
+        /// 获取一个DI点位数值
+        /// </summary>
+        /// <param name="dno">DI序号</param>
+        /// <returns>数值</returns>
+        byte GetDnoDi(short dno);
+
+        /// <summary>
+        /// 获取一个DI逻辑组中的数值集
+        /// </summary>
+        /// <param name="grpno"></param>
+        /// <returns>数值集</returns>
+        short GetGrpDi(short grpno);
+
+        /// <summary>
+        /// 获取多个DI包的数值集合, 每个包有16位数据
+        /// </summary>
+        /// <param name="packIndex"></param>
+        /// <param name="packCnt"></param>
+        /// <returns></returns>
+        ushort[] GetPackDi(short packIndex, short packCnt);
+
+        /// <summary>
+        /// 获取一个DO点位数值
+        /// </summary>
+        /// <param name="dno">DO序号</param>
+        /// <returns>数值</returns>
+        byte GetDnoDo(short dno);
+
+        /// <summary>
+        /// 获取一个DO逻辑组中的数值集
+        /// </summary>
+        /// <param name="grpno"></param>
+        /// <returns>数值集</returns>
+        short GetGrpDo(short grpno);
+
+        /// <summary>
+        /// 获取多个DO包的数值集合, 每个包有16位数据
+        /// </summary>
+        /// <param name="packIndex"></param>
+        /// <param name="packCnt"></param>
+        /// <returns></returns>
+        ushort[] GetPackDo(short packIndex, short packCnt);
+
+        /// <summary>
+        /// 设置一个DO点位数值
+        /// </summary>
+        /// <param name="dno">DO序号</param>
+        /// <param name="value"></param>
+        /// <returns>命令返回码</returns>
+        short SetDnoDo(short dno, short value);
+
+        /// <summary>
+        /// 获取一个AD点位数值
+        /// </summary>
+        /// <param name="dno">DO序号</param>
+        /// <returns>数值</returns>
+        short GetDnoAd(short dno);
+
+        /// <summary>
+        /// 批量获取多个AD点位数值
+        /// </summary>
+        /// <param name="adIndex"></param>
+        /// <param name="adCnt"></param>
+        /// <returns></returns>
+        short[] GetGrpAd(short adIndex, short adCnt);
+
+        /// <summary>
+        /// 写入SDO数据
+        /// </summary>
+        /// <param name="motionSdo">Sdo对象</param>
+        /// <returns>命令返回码</returns>
+        short WriteSdoData(MotionSdo motionSdo);
+
+        /// <summary>
+        /// 写入SDO数据
+        /// </summary>
+        /// <param name="motionSdo">Sdo对象</param>
+        /// <returns>读取的SDO数据长度</returns>
+        int ReadSdoData(MotionSdo motionSdo);
+
+        /// <summary>
+        /// 初始化Pdo配置
+        /// </summary>
+        /// <returns></returns>
+        short GetPdoConfig();
+
+        /// <summary>
+        /// 写入PDO数据
+        /// </summary>
+        /// <param name="motionPdo">Pdo对象</param>
+        /// <returns></returns>
+        short WritePdoData(MotionPdo motionPdo);
+
+        /// <summary>
+        /// 读取Pdo数据
+        /// </summary>
+        /// <param name="motionPdo">Pdo对象</param>
+        /// <returns>读取的PDO数据长度</returns>
+        int ReadPdoData(MotionPdo motionPdo);
+
+        uint WriteUserCode(string data);
+
+        uint CheckUserCode(string data);
+
+        uint WriteUserData(string data);
+
+        uint ReadUserData(ref StringBuilder dataBuilder, short len);
+    }
+}

+ 82 - 0
SKMC.API/Motion/Driver/IMotionDriverAdvance.cs

@@ -0,0 +1,82 @@
+using SKMC.Api.Motion.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Motion.Driver
+{
+    /// <summary>
+    /// 运动控制高级功能接口
+    /// </summary>
+    public interface IMotionDriverAdvance
+    {
+        /// <summary>
+        /// 创建多轴插补坐标
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <param name="axisNos">关联的电机轴集合</param>
+        /// <returns>执行结果, 0执行成功, 其他为错误码</returns>
+        int CreateMultiInterpSys(short InterpNo, short[] axisNos);
+
+        /// <summary>
+        /// 删除多轴插补坐标
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <returns>执行结果, 0执行成功, 其他为错误码</returns>
+        int RemoveMultiInterpSys(short InterpNo);
+
+        /// <summary>
+        /// 添加多轴插补数据(两轴)
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <param name="axis1Data">轴1的绝对位置集合,需要关联CreateMultiInterpSys的axisNos[0]</param>
+        /// <param name="axis2Data">轴2的绝对位置集合,需要关联CreateMultiInterpSys的axisNos[1]</param>
+        /// <param name="speed">基础速度</param>
+        /// <param name="acc">基础加速度</param>
+        /// <param name="blendType">过渡类型</param>
+        /// <param name="blendRatio">过渡曲率</param>
+        /// <returns></returns>
+        int AddMultiInterpData(short InterpNo, List<double> axis1Data, List<double> axis2Data, 
+            double speed, double acc, short blendType = 1, double blendRatio = 0.1);
+
+        /// <summary>
+        /// 清除多轴插补数据
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <returns></returns>
+        int ClearMultiInterpData(short InterpNo);
+
+        /// <summary>
+        /// 清除多轴插补错误/报警
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <returns></returns>
+        int ClearMultiInterpError(short InterpNo);
+
+        /// <summary>
+        /// 开始多轴插补移动
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <returns></returns>
+        int StartMultiInterpMove(short InterpNo);
+
+        /// <summary>
+        /// 等待多轴插补移动到位
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <param name="timeout">超时时间(毫秒)</param>
+        /// <param name="errAction">运行异常或超时后的处理</param>
+        /// <returns></returns>
+        int WaitMultiInterpArrival(short InterpNo, int timeout, Action errAction);
+
+        /// <summary>
+        /// 停止多轴插补移动(到位后需要调用)
+        /// </summary>
+        /// <param name="InterpNo">插补系编号</param>
+        /// <returns></returns>
+        int StopMultiInterpMove(short InterpNo);
+
+    }
+}

+ 48 - 0
SKMC.API/Motion/Driver/IMotionDriverManager.cs

@@ -0,0 +1,48 @@
+
+namespace SKMC.Api.Motion.Driver
+{
+    /// <summary>
+    /// 运动控制卡管理器接口
+    /// </summary>
+    public interface IMotionDriverManager
+    {
+        /// <summary>
+        /// 创建控制卡对象
+        /// </summary>
+        /// <param name="driverName"></param>
+        /// <returns></returns>
+        IMotionDriver Create(string driverName);
+
+        /// <summary>
+        /// 创建控制卡高级接口
+        /// </summary>
+        /// <param name="motionControl"></param>
+        /// <returns></returns>
+        //IMotionAdvanceControl Create(IMotionCard motionControl);
+
+        /// <summary>
+        /// 打开控制卡并初始化
+        /// </summary>
+        /// <param name="driverName"></param>
+        /// <returns></returns>
+        void Open(IMotionDriver motionControl);
+
+        /// <summary>
+        /// 关闭控制卡
+        /// </summary>
+        void Close();
+
+        /// <summary>
+        /// 获取控制层接口
+        /// </summary>
+        /// <returns></returns>
+        IMotionDriver GetMotionControl();
+
+        /// <summary>
+        /// 获取Ethercat总线连接状态
+        /// </summary>
+        /// <returns></returns>
+        bool GetEcatStatus();
+
+    }
+}

+ 47 - 0
SKMC.API/Motion/Driver/IMotionDriverParser.cs

@@ -0,0 +1,47 @@
+using SKMC.Api.Motion.Model;
+using System.Collections.Generic;
+
+namespace SKMC.Api.Motion.Driver
+{
+    /// <summary>
+    /// 运动控制数据解析器接口
+    /// </summary>
+    public interface IMotionDriverParser
+    {
+        /// <summary>
+        /// 是否开启自动触发模式
+        /// 开启后DI能接收到传感器数据
+        /// 关闭后适用于空跑测试
+        /// </summary>
+        /// <param name="trigger"></param>
+        void TriggerMode(bool triggerMode);
+
+        /// <summary>
+        /// 解析一组IO模组的Di/Do返回值, 并注入到数据模型中
+        /// </summary>
+        /// <param name="motionIODev"></param>
+        /// <param name="grpValue1"></param>
+        /// <param name="grpValue2"></param>
+        void ParseDio(MotionIODev motionIODev, short grpValue1, short grpValue2 = -1);
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="motionIODev"></param>
+        /// <param name="grpValue"></param>
+        void ParseDio(MotionIODev motionIODev, ushort grpValue);
+
+        /// <summary>
+        /// 解析多个IO模组的数据
+        /// </summary>
+        void ParseDio(List<MotionIODev> motionIODevs, ushort[] packValues);
+
+        /// <summary>
+        /// 解析一个Axis状态并注入到数据模型中
+        /// </summary>
+        /// <param name="axisStatus"></param>
+        /// <param name="axisSts"></param>
+        void ParseAxisSts(MotionAxisStatus axisStatus, int axisSts);
+
+    }
+}

+ 146 - 0
SKMC.API/Motion/Model/MotionAO.cs

@@ -0,0 +1,146 @@
+using Prism.Mvvm;
+using SKMC.Api.Common.Types;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// 模拟量模型
+    /// </summary>
+    public class MotionAO : BindableBase
+    {
+
+        private long _id;
+
+        public long Id
+        {
+            get { return _id; }
+            set { _id = value; RaisePropertyChanged(); }
+        }
+
+
+        private string _type;
+
+        public string Type
+        {
+            get { return _type; }
+            set { _type = value; RaisePropertyChanged(); }
+        }
+
+        private string _code;
+
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; RaisePropertyChanged(); }
+        }
+
+        private string _name;
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; RaisePropertyChanged(); }
+        }
+
+        private string catalog;
+
+        /// <summary>
+        /// 所在(模块)分类
+        /// </summary>
+        public string Catalog
+        {
+            get { return catalog; }
+            set { catalog = value; RaisePropertyChanged(); }
+        }
+
+        private short _devNo;
+
+        /// <summary>
+        /// 设备序号
+        /// </summary>
+        public short DevNo
+        {
+            get { return _devNo; }
+            set { _devNo = value; }
+        }
+
+        private short _siteNO;
+
+        /// <summary>
+        /// 在设备上的点位顺序, 从0开始
+        /// </summary>
+        public short SiteNo
+        {
+            get { return _siteNO; }
+            set { _siteNO = value; }
+        }
+
+        private short _capacity;
+
+        public short Capacity
+        {
+            get { return _capacity; }
+            set { _capacity = value; }
+        }
+
+
+        private string _desc;
+
+        public string Desc
+        {
+            get { return _desc; }
+            set { _desc = value; RaisePropertyChanged(); }
+        }
+
+        private string _unit;
+
+        /// <summary>
+        /// 数值单位, eg: Kg/N/KPa...
+        /// </summary>
+        public string Unit
+        {
+            get { return _unit; }
+            set { _unit = value; }
+        }
+
+        private decimal radio;
+
+        /// <summary>
+        /// 数值换算比率
+        /// </summary>
+        public decimal Radio
+        {
+            get { return radio; }
+            set { radio = value; RaisePropertyChanged(); }
+        }
+
+        private int offset;
+
+        /// <summary>
+        /// 补偿值 (在比率换算后补偿)
+        /// </summary>
+        public int Offset
+        {
+            get { return offset; }
+            set { offset = value; RaisePropertyChanged(); }
+        }
+
+
+        private TimestampSet timestampSet;
+
+        /// <summary>
+        /// 时间戳数据集
+        /// </summary>
+        public TimestampSet TimestampSet
+        {
+            get { return timestampSet; }
+            set { timestampSet = value; }
+        }
+
+    }
+}

+ 383 - 0
SKMC.API/Motion/Model/MotionAxis.cs

@@ -0,0 +1,383 @@
+using Prism.Mvvm;
+using System;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// 电机模型
+    /// </summary>
+    public class MotionAxis : BindableBase
+    {
+        private long _id;
+
+        public long Id
+        {
+            get { return _id; }
+            set { _id = value; RaisePropertyChanged(); }
+        }
+
+        private string _type;
+
+        /// <summary>
+        /// 电机类型, Closed: 闭环, Open: 开环
+        /// </summary>
+        public string Type
+        {
+            get { return _type; }
+            set { _type = value; }
+        }
+
+        private string _code;
+
+
+        private short level;
+
+        /// <summary>
+        /// 安全等级, 0: 低压电机, 1: 高压电机
+        /// </summary>
+        public short Level
+        {
+            get { return level; }
+            set { level = value; }
+        }
+
+        /// <summary>
+        /// 电机码
+        /// </summary>
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; RaisePropertyChanged(); }
+        }
+
+        private string catalog;
+
+        /// <summary>
+        /// 所属(模块)分类
+        /// </summary>
+        public string Catalog
+        {
+            get { return catalog; }
+            set { catalog = value; RaisePropertyChanged(); }
+        }
+
+
+        private short _axisNo;
+
+        /// <summary>
+        /// 电机序号
+        /// </summary>
+        public short AxisNo
+        {
+            get { return _axisNo; }
+            set { _axisNo = value; RaisePropertyChanged(); }
+        }
+
+        private char _axisDir;
+
+        public char AxisDir
+        {
+            get { return _axisDir; }
+            set { _axisDir = value; RaisePropertyChanged(); }
+        }
+
+        /// <summary>
+        /// 默认的安全检查函数, 用于电机调试时
+        /// </summary>
+        public Func<bool> SafeChecker { get; set; }
+
+        private MotionAxisStatus _axisSts;
+
+        /// <summary>
+        /// 电机状态
+        /// </summary>
+        public MotionAxisStatus AxisSts
+        {
+            get { return _axisSts; }
+            set { _axisSts = value; RaisePropertyChanged(); }
+        }
+
+        /// <summary>
+        /// 到位时允许的偏差 (mm)
+        /// </summary>
+        public double InRange { get; set; }
+
+        /// <summary>
+        /// 是否已停止
+        /// </summary>
+        public bool IsStopped => _axisSts.IsBusy == 0 && _axisSts.IsDone == 1;
+
+        /// <summary>
+        /// 是否上使能
+        /// </summary>
+        public bool IsEnabled => _axisSts.IsEnable == 1;
+
+        /// <summary>
+        /// <para>如果该轴出现异常, 是否会中断其他电机与IO控制</para>
+        /// <para>如果需要对该轴出现异常后进行恢复处理, 为不影响到其他电机与IO控制, 这里需要设置为false</para>
+        /// </summary>
+        public bool CanInterruptAll { get; set; } = true;
+
+        /// <summary>
+        /// 是否已回零
+        /// </summary>
+        public bool IsHomed
+        {
+            get => _axisSts.IsHomed == 1;
+            set => _axisSts.IsHomed = value ? (byte)1 : (byte)0;
+        }
+
+
+        private string _name;
+
+        /// <summary>
+        /// 名称/描述
+        /// </summary>
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; RaisePropertyChanged(); }
+        }
+
+        private string _ptag;
+
+        /// <summary>
+        /// 正方向运动标签, 默认→
+        /// </summary>
+        public string PTag
+        {
+            get { return _ptag; }
+            set { _ptag = value; }
+        }
+
+        private string _ntag;
+
+        /// <summary>
+        /// 负方向运动标签, 默认←
+        /// </summary>
+        public string NTag
+        {
+            get { return _ntag; }
+            set { _ntag = value; }
+        }
+
+
+        private short _homeMod;
+
+        /// <summary>
+        /// 驱动器回零模式
+        /// </summary>
+        public short HomeMod
+        {
+            get { return _homeMod; }
+            set { _homeMod = value; RaisePropertyChanged(); }
+        }
+
+        private short _homeDir;
+
+        /// <summary>
+        /// 回零方向
+        /// </summary>
+        public short HomeDir
+        {
+            get { return _homeDir; }
+            set { _homeDir = value; RaisePropertyChanged(); }
+        }
+
+        private double _lowVel;
+
+        /// <summary>
+        /// 回零低速
+        /// </summary>
+        public double LowVel
+        {
+            get { return _lowVel; }
+            set { _lowVel = value; RaisePropertyChanged(); }
+        }
+
+        private double _highVel;
+
+        /// <summary>
+        /// 回零高速
+        /// </summary>
+        public double HighVel
+        {
+            get { return _highVel; }
+            set { _highVel = value; RaisePropertyChanged(); }
+        }
+
+        public string SerialNo { get => $"[{AxisNo}]{AxisDir}: {Code}"; }
+
+        public string SerialName { get => $"[{Code}]: {Name}"; }
+
+
+        private int _gearScale;
+
+        /// <summary>
+        /// 电子齿轮比
+        /// </summary>
+        public int GearScale
+        {
+            get { return _gearScale; }
+            set { _gearScale = value; }
+        }
+
+        private double _distanceR;
+
+        /// <summary>
+        /// 电机每旋转一圈机构的移动距离, 可以是螺距导程, 也可以是传动轮周长
+        /// </summary>
+        public double DistanceR
+        {
+            get { return _distanceR; }
+            set { _distanceR = value; }
+        }
+
+        private short _unitType;
+
+        // 单位类型, 0表示电机步距, 1表示实际距离
+        public short UnitType
+        {
+            get { return _unitType; }
+            set { _unitType = value; }
+        }
+
+
+        private string _unitName;
+
+        // 单位名称用于显示, 例如mm, °等
+        public string UnitName
+        {
+            get { return _unitName; }
+            set { _unitName = value; }
+        }
+
+        private string _moveType;
+
+        // 移动类型, L直线, R旋转
+        public string MoveType
+        {
+            get { return _moveType; }
+            set { _moveType = value; }
+        }
+
+        /// <summary>
+        /// 距离(mm)对应的电机运动单位
+        /// </summary>
+        public double DistanceUnit
+        {
+            get => _gearScale / _distanceR;
+        }
+
+        /// <summary>
+        /// 当前ENC值对应的实际距离mm
+        /// </summary>
+        public double Distance
+        {
+            get => _axisSts.ENC / DistanceUnit;
+        }
+
+        /// <summary>
+        /// 离上一次记录的实际距离 mm
+        /// </summary>
+        public double WatchDistance => _axisSts.WatchPos / DistanceUnit;
+
+        private double _prfUnit;
+
+        /// <summary>
+        /// 运动目标值, 实际单位mm或者角度等
+        /// </summary>
+        public double PrfUnit
+        {
+            get { return _prfUnit; }
+            set
+            {
+                _prfUnit = value / DistanceUnit;
+                AxisSts.PRF = value;
+                RaisePropertyChanged();
+            }
+        }
+
+        private double _encUnit;
+
+        /// <summary>
+        /// 运动反馈值, 实际单位mm或者角度等
+        /// </summary>
+        public double EncUnit
+        {
+            get { return _encUnit; }
+            set
+            {
+                _encUnit = value / DistanceUnit;
+                AxisSts.ENC = value;
+                if (PercentOn) Percent = (_encUnit - NegativeUnit) / (PositiveUnit - NegativeUnit);
+                RaisePropertyChanged();
+            }
+        }
+
+        /// <summary>
+        /// 是否使用位置百分比
+        /// </summary>
+        public bool PercentOn { get; set; }
+
+        /// <summary>
+        /// 负限位位置(mm)
+        /// </summary>
+        public double NegativeUnit { get; set; }
+
+        /// <summary>
+        /// 正限位位置(mm)
+        /// </summary>
+        public double PositiveUnit { get; set; }
+
+
+        private double percent;
+
+        /// <summary>
+        /// 位置百分比, 负限位为0%, 正限位为100%
+        /// </summary>
+        public double Percent
+        {
+            get { return percent; }
+            set { percent = value; RaisePropertyChanged(); }
+        }
+
+        /// <summary>
+        /// 电机停止(中断)
+        /// </summary>
+        public bool IsInterrupt { get; set; }
+
+        /// <summary>
+        /// 被占用的handler
+        /// </summary>
+        public int? LockHandler { get; set; }
+
+
+        /// <summary>
+        /// 当前位置是否在偏移量之内
+        /// </summary>
+        /// <param name="targetVal"></param>
+        /// <param name="offset"></param>
+        /// <returns></returns>
+        public bool IsEncInRange(double targetVal)
+        {
+            if (_encUnit == targetVal) return true;
+            else if (_encUnit > targetVal) return _encUnit - targetVal <= InRange;
+            else return targetVal - _encUnit <= InRange;
+        }
+
+        /// <summary>
+        /// 当前位置是否在偏移量之内
+        /// </summary>
+        /// <param name="targetVal"></param>
+        /// <param name="offset"></param>
+        /// <returns></returns>
+        public bool IsEncInRange(double targetVal, double offset)
+        {
+            if (_encUnit == targetVal) return true;
+            else if (_encUnit > targetVal) return _encUnit - targetVal <= offset;
+            else return targetVal - _encUnit <= offset;
+        }
+
+    }
+}

+ 65 - 0
SKMC.API/Motion/Model/MotionAxisParam.cs

@@ -0,0 +1,65 @@
+
+namespace SKMC.Api.Motion.Model
+{
+    public class AxisHomeParam
+    {
+        // 回零模式
+        public short HomeMod { get; set; }
+        // 回零低速
+        public int LowVel { get; set; }
+        // 回零高速
+        public int HighVel { get; set; }
+        // 
+        public int StopVel { get; set; }
+        // 加速度
+        public int Acc { get; set; }
+        // 减速度
+        public int Dec { get; set; }
+        public double Overtime { get; set; }
+        public double Offset { get; set; }
+
+        public AxisHomeParam() { }
+
+        public AxisHomeParam(AxisMoveParma moveParam)
+        {
+            LowVel = moveParam.MaxVel / 10;
+            HighVel = moveParam.MaxVel / 2;
+            Acc = moveParam.Acc;
+            Dec = moveParam.Dec;
+        }
+
+        public AxisHomeParam(AxisMoveParma moveParam, short homeMod)
+        {
+            HomeMod = homeMod;
+            LowVel = moveParam.MaxVel / 10;
+            HighVel = moveParam.MaxVel / 2;
+            Acc = moveParam.Acc;
+            Dec = moveParam.Dec;
+        }
+    }
+
+    public class AxisMoveParma
+    {
+        public int MinVel { get; set; }
+        public int MaxVel { get; set; }
+        public int StopVel { get; set; }
+        public int Acc { get; set; }
+        public int Dec { get; set; }
+        // 加速时间
+        public double Tacc { get; set; }
+        // 减速时间
+        public double Tdec { get; set; }
+        // S型Radio
+        public double Radio { get; set; }
+        // 目标位置
+        public double Pos { get; set; }
+        // 设置连续运行时的最后速度
+        public double FinalVel { get; set; }
+
+        public override string ToString()
+        {
+            return $"vel: {MaxVel}, acc: {Acc}, dec: {Dec}, radio: {Radio}";
+        }
+    }
+
+}

+ 26 - 0
SKMC.API/Motion/Model/MotionAxisRecord.cs

@@ -0,0 +1,26 @@
+using System;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// 电机运行记录
+    /// </summary>
+    public struct MotionAxisRecord
+    {
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        public long StartTicks { get; set; }
+
+        /// <summary>
+        /// 用时
+        /// </summary>
+        public long UsingTicks { get => DateTime.Now.Ticks - StartTicks; }
+
+        /// <summary>
+        /// 开始位置
+        /// </summary>
+        public double StartPos { get; set; }
+
+    }
+}

+ 153 - 0
SKMC.API/Motion/Model/MotionAxisStatus.cs

@@ -0,0 +1,153 @@
+using Prism.Mvvm;
+using System;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// 电机状态模型
+    /// </summary>
+    public class MotionAxisStatus : BindableBase
+    {
+        private byte _isEnable;
+
+        /// <summary>
+        /// 轴是否打开使能
+        /// </summary>
+        public byte IsEnable
+        {
+            get { return _isEnable; }
+            set { _isEnable = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isBusy;
+
+        /// <summary>
+        /// 轴是否忙(运动)
+        /// </summary>
+        public byte IsBusy
+        {
+            get { return _isBusy; }
+            set { _isBusy = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isOutBound;
+
+        /// <summary>
+        /// 轴是否越限
+        /// </summary>
+        public byte IsOutBound
+        {
+            get { return _isOutBound; }
+            set { _isOutBound = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isDone;
+
+        /// <summary>
+        /// 轴是否到位
+        /// </summary>
+        public byte IsDone
+        {
+            get { return _isDone; }
+            set { _isDone = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isAlarm;
+
+        /// <summary>
+        /// 轴是否报警
+        /// </summary>
+        public byte IsAlarm
+        {
+            get { return _isAlarm; }
+            set { _isAlarm = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isHomed;
+
+        /// <summary>
+        /// 轴是否已回零
+        /// </summary>
+        public byte IsHomed
+        {
+            get { return _isHomed; }
+            set { _isHomed = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isBreak;
+
+        /// <summary>
+        /// 轴是否已刹车
+        /// </summary>
+        public byte IsBreak
+        {
+            get { return _isBreak; }
+            set { _isBreak = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isORG;
+
+        /// <summary>
+        /// 是否触发原点
+        /// </summary>
+        public byte IsORG
+        {
+            get { return _isORG; }
+            set { _isORG = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isPEL;
+
+        /// <summary>
+        /// 是否触发正限位
+        /// </summary>
+        public byte IsPEL
+        {
+            get { return _isPEL; }
+            set { _isPEL = value; RaisePropertyChanged(); }
+        }
+
+        private byte _isNEL;
+
+        /// <summary>
+        /// 是否触发负限位
+        /// </summary>
+        public byte IsNEL
+        {
+            get { return _isNEL; }
+            set { _isNEL = value; RaisePropertyChanged(); }
+        }
+
+        private double _prf;
+
+        /// <summary>
+        /// 运行目标值
+        /// </summary>
+        public double PRF
+        {
+            get { return _prf; }
+            set { _prf = value; RaisePropertyChanged(); }
+        }
+
+        private double _enc;
+
+        /// <summary>
+        /// 当前反馈值
+        /// </summary>
+        public double ENC
+        {
+            get { return _enc; }
+            set { _enc = value; RaisePropertyChanged(); }
+        }
+
+        #region watch data
+        public long StartTicks { get; set; }
+
+        public long WatchTicks { get => DateTime.Now.Ticks - StartTicks; }
+
+        public double StartPos { get; set; }
+
+        public double WatchPos { get => _enc - StartPos; }
+        #endregion
+    }
+}

+ 35 - 0
SKMC.API/Motion/Model/MotionCnd.cs

@@ -0,0 +1,35 @@
+
+namespace SKMC.Api.Motion.Model
+{
+    public class MotionCnd
+    {
+
+        public static CndExpress Where()
+        {
+            return new CndExpress();
+        }
+
+        public class CndExpress
+        {
+            public string DioName { get; set; }
+
+            public string Expression { get; set; }
+
+            public CndExpress Eq(string dioName, object value)
+            {
+                return null;
+            }
+
+            public CndExpress Ge(string dioName, object value)
+            {
+                return null;
+            }
+
+            public CndExpress Le(string dioName, object value)
+            {
+                return null;
+            }
+        }
+
+    }
+}

+ 236 - 0
SKMC.API/Motion/Model/MotionIO.cs

@@ -0,0 +1,236 @@
+using Prism.Mvvm;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// IO模型
+    /// </summary>
+    public class MotionIO : BindableBase
+    {
+        public int VALUE_COUNTER_MAX { get; set; } = 9999;
+
+        private string _type;
+
+        public string Type
+        {
+            get { return _type; }
+            set { _type = value; RaisePropertyChanged(); }
+        }
+
+        private string _code;
+
+        public string Code
+        {
+            get { return _code; }
+            set { _code = value; RaisePropertyChanged(); }
+        }
+
+        private string catalog;
+
+        /// <summary>
+        /// 所在(模块)分类
+        /// </summary>
+        public string Catalog
+        {
+            get { return catalog; }
+            set { catalog = value; RaisePropertyChanged(); }
+        }
+
+
+        private byte _value = 255;
+
+        /// <summary>
+        /// 当前数值
+        /// </summary>
+        public byte Value
+        {
+            get { return _value; }
+            set 
+            {
+                // value changed
+                if (_value != value)
+                {
+                    _valueConunter = 0;
+                    _valueLast = _value;
+                }
+                _value = value;
+                if (_valueConunter < VALUE_COUNTER_MAX) _valueConunter++;
+                RaisePropertyChanged(); 
+            }
+        }
+
+        private byte _valueLast = 255;
+
+        /// <summary>
+        /// 前一个数值
+        /// </summary>
+        public byte ValueLast
+        {
+            get => _valueLast;
+            set => _valueLast = value;
+        }
+
+        private int _valueConunter;
+
+        /// <summary>
+        /// 数值计数
+        /// </summary>
+        public int ValueCounter
+        {
+            get { return _valueConunter; }
+            set { _valueConunter = value; }
+        }
+
+        public string ValueTxt { get => "■"; }
+
+        private bool _testMode;
+
+        /// <summary>
+        /// 测试模式下不获取数据
+        /// </summary>
+        public bool TestMode
+        {
+            get { return _testMode; }
+            set { _testMode = value; }
+        }
+
+        private string _name;
+
+        public string Name
+        {
+            get { return _name; }
+            set { _name = value; RaisePropertyChanged(); }
+        }
+
+        private short _devNo;
+
+        /// <summary>
+        /// IO模组设备序号
+        /// </summary>
+        public short DevNo
+        {
+            get { return _devNo; }
+            set { _devNo = value; }
+        }
+
+
+        private short _siteNO;
+
+        /// <summary>
+        /// 在设备上的点位顺序, 从0开始
+        /// </summary>
+        public short SiteNo
+        {
+            get { return _siteNO; }
+            set { _siteNO = value; }
+        }
+
+        private short _siteNoMutex;
+
+        /// <summary>
+        /// 点位互斥设置
+        /// </summary>
+        public short SiteNoMutex
+        {
+            get { return _siteNoMutex; }
+            set { _siteNoMutex = value; }
+        }
+
+
+        private short _grpNo = 0;
+
+        /// <summary>
+        /// 逻辑组序号
+        /// </summary>
+        public short GrpNo
+        {
+            get { return _grpNo; }
+            set { _grpNo = value; }
+        }
+
+        private short _bitNo;
+
+        /// <summary>
+        /// BitNo是IO在逻辑组的序号
+        /// </summary>
+        public short BitNo
+        {
+            get { return _bitNo; }
+            set { _bitNo = value; }
+        }
+
+        private short _dioNo;
+
+        /// <summary>
+        /// DioNo是IO在全局的序号, 可通过GrpNo、BitNo组合计算
+        /// </summary>
+        public short DioNo
+        {
+            get { return _dioNo; }
+            set { _dioNo = value; }
+        }
+
+        public string SerialName { get => $"[{Code}]: {Name}"; }
+
+        /// <summary>
+        /// 一组8位数据的映射方法
+        /// </summary>
+        /// <returns></returns>
+        public MotionIO Set8Bit()
+        {
+            _grpNo = _siteNO >= 8 ? (short)(_devNo * 2 + 1) : (short)(_devNo * 2);
+            _bitNo = _siteNO >= 8 ? (short)(_siteNO - 8) : _siteNO;
+            _dioNo = (short)(_grpNo * 8 + _bitNo);
+            return this;
+        }
+
+        /// <summary>
+        /// 一组16位数据的映射方法
+        /// </summary>
+        /// <returns></returns>
+        public MotionIO Set16Bit()
+        {
+            _grpNo = _devNo;
+            _bitNo = _siteNO;
+            _dioNo = (short)(_grpNo * 16 + _bitNo);
+            return this;
+        }
+
+        public void Reset()
+        {
+            _value = 0;
+            _valueLast = 0;
+            _valueConunter = 0;
+        }
+
+        private short _invert = 0;
+
+        /// <summary>
+        /// 是否取反
+        /// </summary>
+        public short Invert
+        {
+            get { return _invert; }
+            set { _invert = value; RaisePropertyChanged(); }
+        }
+
+        private short _trigger;
+
+        // 是否为触发器 (用于流程触发)
+        public short Trigger
+        {
+            get { return _trigger; }
+            set { _trigger = value; }
+        }
+
+
+        private string _desc;
+
+        public string Desc
+        {
+            get { return _desc; }
+            set { _desc = value; RaisePropertyChanged(); }
+        }
+
+    }
+}

+ 70 - 0
SKMC.API/Motion/Model/MotionIODev.cs

@@ -0,0 +1,70 @@
+using SKMC.Api.Common;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// IO模块设备
+    /// </summary>
+    public class MotionIODev
+    {
+        /// <summary>
+        /// 设备编号
+        /// </summary>
+        public short DevNo { get; set; }
+
+        /// <summary>
+        /// 设备类型, DI/DO/AD/DA
+        /// </summary>
+        public string Type { get; set; }
+
+        /// <summary>
+        /// 当前的分类, 在分类筛选时有用
+        /// </summary>
+        public string Catalog { get; set; }
+
+        /// <summary>
+        /// 所属(模块)分类, 一个IODev可能包含多个分类
+        /// </summary>
+        public HashSet<string> Catalogs { get; set; } = new HashSet<string>();
+
+        public string Desc
+        {
+            get
+            {
+                if (MotionIOs != null & MotionIOs.Count > 0)
+                {
+                    HashSet<string> descs = new HashSet<string>();
+                    foreach(var motionIO in MotionIOs)
+                    {
+                        if (!CommonUtil.IsEmptyString(motionIO.Code))
+                        {
+                            descs.Add(motionIO.Desc);
+                        }
+                    }
+                    StringBuilder builder = new StringBuilder();
+                    foreach(var desc in descs)
+                    {
+                        builder.Append(desc).Append(',');
+                    }
+                    return builder.Remove(builder.Length - 1, 1).ToString();
+                }
+                return default;
+            }
+        }
+
+        /// <summary>
+        /// 点位数量
+        /// </summary>
+        public int SiteNum { get => MotionIOs.Count; }
+
+        public List<MotionIO> MotionIOs { get; set; }
+
+        public string DevInfo() => $"{Type} [{DevNo}]: {Desc}";
+
+    }
+}

+ 21 - 0
SKMC.API/Motion/Model/MotionIOGroup.cs

@@ -0,0 +1,21 @@
+using System.Collections.ObjectModel;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// IO分组
+    /// </summary>
+    public class MotionIOGroup
+    {
+        // DI / DO
+        public string Type { get; set; }
+
+        public int GrpNo { get; set; }
+
+        // extension
+        public string Station { get; set; }
+
+        public ObservableCollection<MotionIO> DeviceIOModels { get; set; }
+
+    }
+}

+ 33 - 0
SKMC.API/Motion/Model/MotionPdo.cs

@@ -0,0 +1,33 @@
+using SKMC.Api.Common.Types;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// PDO数据模型
+    /// </summary>
+    public class MotionPdo : MotionSdo
+    {
+        /// <summary>
+        /// 组号
+        /// </summary>
+        public string Group { get; set; }
+
+        /// <summary>
+        /// 读写标识 0:读, 1:写
+        /// </summary>
+        public short RW { get; set; }
+
+
+        private TimestampSet timestampSet;
+
+        /// <summary>
+        /// 时间戳数据集
+        /// </summary>
+        public TimestampSet TimestampSet
+        {
+            get { return timestampSet; }
+            set { timestampSet = value; }
+        }
+
+    }
+}

+ 26 - 0
SKMC.API/Motion/Model/MotionPosition.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// 运动位置
+    /// 二维或者三维数据
+    /// 单位可以是实际位置/距离, 也可以是电机步距
+    /// </summary>
+    public class MotionPosition
+    {
+        /// <summary>
+        /// 二维或者三维的坐标点
+        /// </summary>
+        public double[] Point { get; set; }
+
+        /// <summary>
+        /// 速度参数
+        /// </summary>
+        public int[] SpeedParam { get; set; }
+    }
+}

+ 44 - 0
SKMC.API/Motion/Model/MotionSdo.cs

@@ -0,0 +1,44 @@
+
+namespace SKMC.Api.Motion.Model
+{
+    /// <summary>
+    /// SDO数据模型
+    /// </summary>
+    public class MotionSdo
+    {
+
+        public string Code { get; set; }
+
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Ecat口序号
+        /// </summary>
+        public short EcatPort { get; set; }
+
+        /// <summary>
+        /// Ecat从站序号
+        /// </summary>
+        public short StationNo { get; set; }
+
+        /// <summary>
+        /// 索引值
+        /// </summary>
+        public short Index { get; set; }
+
+        /// <summary>
+        /// 子索引
+        /// </summary>
+        public short SubIndex { get; set; }
+
+        /// <summary>
+        /// 数据长度
+        /// </summary>
+        public short DataSize { get; set; }
+
+        /// <summary>
+        /// 数值(10进制)
+        /// </summary>
+        public int DataVal { get; set; }
+    }
+}

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