Caoshining 1 рік тому
коміт
41598f5085
100 змінених файлів з 10387 додано та 0 видалено
  1. 0 0
      README.md
  2. 144 0
      SHJX.Service.Common/Calculate/SampleCalculate.cs
  3. 15 0
      SHJX.Service.Common/Camera/ColorDataGrabedEventArgs.cs
  4. 603 0
      SHJX.Service.Common/Camera/ColorDataGraber.cs
  5. 152 0
      SHJX.Service.Common/Camera/ColorDataPoint.cs
  6. 57 0
      SHJX.Service.Common/Camera/CreatGraber.cs
  7. 640 0
      SHJX.Service.Common/Camera/ImageRGBanalysis.cs
  8. 579 0
      SHJX.Service.Common/Camera/NoiseDataFilter.cs
  9. 60 0
      SHJX.Service.Common/Camera/SensorsParameter.cs
  10. 10 0
      SHJX.Service.Common/Camera/TitrationSettings.cs
  11. 397 0
      SHJX.Service.Common/Camera/WebCamera.cs
  12. 72 0
      SHJX.Service.Common/CustomUtil/DataCache.cs
  13. 67 0
      SHJX.Service.Common/Extend/CancelCache.cs
  14. 788 0
      SHJX.Service.Common/Extend/Converter.cs
  15. 117 0
      SHJX.Service.Common/Extend/Encryption.cs
  16. 474 0
      SHJX.Service.Common/Extend/ExportWordByNPOI.cs
  17. 83 0
      SHJX.Service.Common/Extend/ExtendUtil.cs
  18. 92 0
      SHJX.Service.Common/Extend/NpoiWordParagraphTextStyle.cs
  19. 30 0
      SHJX.Service.Common/Extend/ObjectConvert.cs
  20. 92 0
      SHJX.Service.Common/ExtendElement/PasswordHelper.cs
  21. 61 0
      SHJX.Service.Common/ExtendElement/UMessageBox.cs
  22. 7 0
      SHJX.Service.Common/Interface/InterceptorImp.cs
  23. 53 0
      SHJX.Service.Common/Logging/LogFactory.cs
  24. 150 0
      SHJX.Service.Common/Logging/ReadXML/AbstractConfiguration.cs
  25. 19 0
      SHJX.Service.Common/Logging/ReadXML/CreateConfig.cs
  26. 141 0
      SHJX.Service.Common/Logging/ReadXML/ReadConfigUtil.cs
  27. 17 0
      SHJX.Service.Common/Model/FlowControlProxyArg.cs
  28. 45 0
      SHJX.Service.Common/Model/LiquidDropArgs.cs
  29. 36 0
      SHJX.Service.Common/Properties/AssemblyInfo.cs
  30. 71 0
      SHJX.Service.Common/Proxy/Proxy.cs
  31. 355 0
      SHJX.Service.Common/SHJX.Service.Common.csproj
  32. 3 0
      SHJX.Service.Common/SHJX.Service.Common.csproj.user
  33. 28 0
      SHJX.Service.Common/UserColor/UColor.cs
  34. 37 0
      SHJX.Service.Common/UserDelegate/Messager.cs
  35. 76 0
      SHJX.Service.Common/UserTimer/TimerProcessor.cs
  36. 187 0
      SHJX.Service.Common/Utils/Converts.cs
  37. 147 0
      SHJX.Service.Common/app.config
  38. 60 0
      SHJX.Service.Common/packages.config
  39. 32 0
      SHJX.Service.Control/Common/OptionSilverSulfatePipe.cs
  40. 216 0
      SHJX.Service.Control/ControlProxy/ProxyRouteContext.cs
  41. 105 0
      SHJX.Service.Control/CreateTask.cs
  42. 542 0
      SHJX.Service.Control/DataCentre.cs
  43. 58 0
      SHJX.Service.Control/FlowContent.cs
  44. 25 0
      SHJX.Service.Control/Interface/ChangeTaskStateImp.cs
  45. 28 0
      SHJX.Service.Control/Interface/EquipmentFlowImp.cs
  46. 16 0
      SHJX.Service.Control/Interface/FlowControlOperateImp.cs
  47. 27 0
      SHJX.Service.Control/Interface/WriteTaskToEquipmentImp.cs
  48. 43 0
      SHJX.Service.Control/Modules/DataEventArgs.cs
  49. 31 0
      SHJX.Service.Control/PortOperate/Content/PumpContent.cs
  50. 39 0
      SHJX.Service.Control/PortOperate/Content/StorageContent.cs
  51. 17 0
      SHJX.Service.Control/PortOperate/DissolveFanOperate.cs
  52. 38 0
      SHJX.Service.Control/PortOperate/DripNozzleOperate.cs
  53. 38 0
      SHJX.Service.Control/PortOperate/DripNozzleOperateHigh.cs
  54. 26 0
      SHJX.Service.Control/PortOperate/FasHighOperate.cs
  55. 32 0
      SHJX.Service.Control/PortOperate/FasLowOperate.cs
  56. 17 0
      SHJX.Service.Control/PortOperate/IndicatorOperate.cs
  57. 17 0
      SHJX.Service.Control/PortOperate/IndicatorOperateHigh.cs
  58. 187 0
      SHJX.Service.Control/PortOperate/Interface/PortOperateImp.cs
  59. 44 0
      SHJX.Service.Control/PortOperate/Interface/StorageOperateImp.cs
  60. 20 0
      SHJX.Service.Control/PortOperate/LiquidStirOperate.cs
  61. 20 0
      SHJX.Service.Control/PortOperate/MercuryOperate.cs
  62. 16 0
      SHJX.Service.Control/PortOperate/PotassiumDichromateHighOperate.cs
  63. 16 0
      SHJX.Service.Control/PortOperate/PotassiumDichromateLowOperatep.cs
  64. 17 0
      SHJX.Service.Control/PortOperate/SampleFanOperate.cs
  65. 43 0
      SHJX.Service.Control/PortOperate/SilverSulfateOperate.cs
  66. 45 0
      SHJX.Service.Control/PortOperate/SilverSulfatePipeOperate.cs
  67. 16 0
      SHJX.Service.Control/PortOperate/TimerOperate.cs
  68. 13 0
      SHJX.Service.Control/PortOperate/TitrationStirOperate.cs
  69. 13 0
      SHJX.Service.Control/PortOperate/TitrationStirOperateHigh.cs
  70. 16 0
      SHJX.Service.Control/PortOperate/WaterOperate.cs
  71. 36 0
      SHJX.Service.Control/Properties/AssemblyInfo.cs
  72. 57 0
      SHJX.Service.Control/Route/RouteController/CageOperate.cs
  73. 19 0
      SHJX.Service.Control/Route/RouteController/CalibrationCoolingOperate.cs
  74. 45 0
      SHJX.Service.Control/Route/RouteController/CoolingCommon.cs
  75. 68 0
      SHJX.Service.Control/Route/RouteController/DissolveAddLiquidOperate.cs
  76. 115 0
      SHJX.Service.Control/Route/RouteController/DissolveAddWaterOperate.cs
  77. 14 0
      SHJX.Service.Control/Route/RouteController/DissolveCoolingOperate.cs
  78. 110 0
      SHJX.Service.Control/Route/RouteController/DissolveMoveOperate.cs
  79. 188 0
      SHJX.Service.Control/Route/RouteController/LiquidHeatingOperate.cs
  80. 23 0
      SHJX.Service.Control/Route/RouteController/LiquidStirOperate.cs
  81. 103 0
      SHJX.Service.Control/Route/RouteController/ManiOperate.cs
  82. 53 0
      SHJX.Service.Control/Route/RouteController/MotorTOperate.cs
  83. 33 0
      SHJX.Service.Control/Route/RouteController/MotorXOperate.cs
  84. 43 0
      SHJX.Service.Control/Route/RouteController/MotorYOperate.cs
  85. 82 0
      SHJX.Service.Control/Route/RouteController/MotorZOperate.cs
  86. 72 0
      SHJX.Service.Control/Route/RouteController/SampleAL1Operate.cs
  87. 94 0
      SHJX.Service.Control/Route/RouteController/SampleAL2Operate.cs
  88. 65 0
      SHJX.Service.Control/Route/RouteController/SampleCoolingOperate.cs
  89. 79 0
      SHJX.Service.Control/Route/RouteController/TitrationAddIndicatorOperate.cs
  90. 557 0
      SHJX.Service.Control/Route/RouteController/TitrationOperate.cs
  91. 532 0
      SHJX.Service.Control/Route/RouteController/TitrationOperate3.cs
  92. 27 0
      SHJX.Service.Control/Route/RouteController/TitrationStirOperate.cs
  93. 27 0
      SHJX.Service.Control/Route/RouteController/TitrationStirOperateHigh.cs
  94. 24 0
      SHJX.Service.Control/Route/RouteFlow/CaFlow.cs
  95. 23 0
      SHJX.Service.Control/Route/RouteFlow/CalibrationCoolingFlow.cs
  96. 23 0
      SHJX.Service.Control/Route/RouteFlow/DissolveAddLiquidFlow.cs
  97. 23 0
      SHJX.Service.Control/Route/RouteFlow/DissolveAddWaterFlow.cs
  98. 27 0
      SHJX.Service.Control/Route/RouteFlow/DissolveCoolingFlow.cs
  99. 24 0
      SHJX.Service.Control/Route/RouteFlow/DissolveMoveFlow.cs
  100. 23 0
      SHJX.Service.Control/Route/RouteFlow/LiquidHeatingFlow.cs

+ 144 - 0
SHJX.Service.Common/Calculate/SampleCalculate.cs

@@ -0,0 +1,144 @@
+using System.Linq;
+using SHJX.Service.Model.Dao;
+using System.Collections.Generic;
+using System;
+namespace SHJX.Service.Common.Calculate
+{
+    public static class SampleCalculate
+    {
+        /// <summary>
+        /// 获取空白样的结果
+        /// </summary>
+        /// <param name="noneTasks"></param>
+        /// <param name="blankValue"></param>
+        public static double[] temp = new double[50];
+        public static int tempCount = 0;
+        public static void GeBlankTaskResult(this List<EquipmentTask> noneTasks, double maxVolume, out double blankValue)
+        {
+            if (noneTasks?.Count==0)
+            {
+                blankValue = 0;
+                return;
+            }
+            if (noneTasks?.Count > 1)
+            {
+                var noneMaxValue = noneTasks.Max(item => item.Amount);
+                var noneMinValue = noneTasks.Min(item => item.Amount);
+                if (noneMaxValue >= maxVolume)
+                {
+                    GeBlankTaskResult( noneTasks.Where(item => item.Amount != noneMaxValue).ToList(), maxVolume, out blankValue);
+                }
+                if (noneMaxValue - noneMinValue > 0.8)
+                {
+                    GeBlankTaskResult( noneTasks.Where(item => item.Amount != noneMinValue).ToList(), maxVolume, out blankValue);
+                }
+                temp[tempCount++] = Math.Round(noneTasks.Average(item => item.Amount), 2);
+                blankValue = temp[0];
+            }
+            else
+            {
+                var first = noneTasks?.FirstOrDefault();
+                if (first?.Amount >= maxVolume)
+                {
+                    temp[tempCount++] = 0;
+                }
+                else
+                    temp[tempCount++] = Math.Round(first?.Amount ?? 0, 2);
+                blankValue = temp[0];
+            }
+            //if (noneTasks?.Count > 1)
+            //{
+            //    var noneMaxValue = noneTasks.Max(item => item.Amount);
+            //    var noneMinValue = noneTasks.Min(item => item.Amount);
+            //    if (noneMaxValue - noneMinValue <= 0.8)
+            //    {
+            //        blankValue = noneTasks.Average(item => item.Amount);
+            //        return;
+            //    }
+            //    if (noneMaxValue >= maxVolume)
+            //    {
+            //        GeBlankTaskResult(noneTasks.Where(item => item.Amount != noneMaxValue).ToList(), maxVolume, out blankValue);
+            //    }
+            //    else
+            //    {
+            //        blankValue = noneMaxValue;
+            //    }
+            //}
+            //else
+            //{
+            //    var first = noneTasks?.FirstOrDefault();
+            //    blankValue = first?.Amount ?? 0;
+            //}
+        }
+
+        public static void GetBDTaskResult(this List<EquipmentTask> bdTasks, double maxVolume, string level, out double bdValue)
+        {
+            if (bdTasks?.Count == 0)
+            {
+                bdValue = 0;
+                return;
+            }
+            double bdAmount = 0;
+            if (bdTasks is not null && bdTasks.Any())
+            {
+                List<EquipmentTask> bdTasksUsed = new List<EquipmentTask>();
+                for (int i = 0; i < bdTasks.Count; i++)
+                {
+                    if (bdTasks[i].Amount < maxVolume)
+                    {
+                        bdTasksUsed.Add(bdTasks[i]);
+                    }
+                }
+                if (bdTasksUsed.Count == 0)
+                {
+                    bdAmount = Math.Round(maxVolume, 2);
+                }
+                else
+                {
+                    bdAmount = Math.Round(bdTasksUsed.Average(item => Convert.ToDouble(item.Amount)), 2);
+                }
+                if (bdAmount == 0)
+                {
+                    bdValue = 0;
+                }
+                else
+                {
+                    bdValue = level switch
+                    {
+                        "Low" => 0.125 / bdAmount,
+                        "High" => 1.25 / bdAmount,
+                        _ => throw new ArgumentNullException(level)
+                    };
+                }
+                bdValue = Math.Round(bdValue, 5);
+            }
+            else
+            {
+                bdAmount = 0;
+                var first = bdTasks?.FirstOrDefault();
+                if (first?.Amount >= maxVolume)
+                    bdAmount = Math.Round(maxVolume, 2); 
+                else
+                {
+                    bdAmount = Math.Round(first?.Amount ?? 0, 2);
+                }
+                if (bdAmount == 0)
+                {
+                    bdValue = 0;
+                }
+                else 
+                { 
+                    bdValue = level switch
+                    {
+                        "Low" => 0.125 / bdAmount,
+                        "High" => 1.25 / bdAmount,
+                        _ => throw new ArgumentNullException(level)
+                    };
+                }
+                 bdValue = Math.Round(bdValue, 5);
+                
+                
+            }
+        }
+    }
+}

+ 15 - 0
SHJX.Service.Common/Camera/ColorDataGrabedEventArgs.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace SHJX.Service.Common.Camera
+{
+    /// <summary>
+    /// 新采样点被采集事件参数
+    /// </summary>
+    public class ColorDataGrabedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// 新的采样点数据
+        /// </summary>
+        public ColorDataPoint ColorPoint;
+    }
+}

+ 603 - 0
SHJX.Service.Common/Camera/ColorDataGraber.cs

@@ -0,0 +1,603 @@
+using System;
+using System.Linq;
+using System.Drawing;
+using System.Threading;
+using System.Diagnostics;
+using System.Collections.Generic;
+using SHJX.Service.Common.UserDelegate;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+using AForge.Video.DirectShow;
+
+namespace SHJX.Service.Common.Camera
+{
+    /// <summary>
+    /// 图像采集器,定时进行图像采集
+    /// 每次采样发布采样事件:ColorDataGrabed
+    /// </summary>
+    public class ColorDataGraber
+    {
+        #region fields
+
+        public bool PrintDebugLog = true; // 是否输出调试日志
+
+        /// <summary>
+        /// 摄像头
+        /// </summary>
+        public WebCamera WebCam { get; set; }
+
+        /// <summary>
+        /// 提取ROI区域色彩数据
+        /// </summary>
+        protected ImageRGBanalysis ColorAnalysis { get; set; }
+
+        /// <summary>
+        /// 停止数据采集
+        /// </summary>
+        protected bool IsStopGrab { get; set; }
+
+        #endregion
+
+        #region properties
+
+
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(ColorDataGraber));
+        /// <summary>
+        /// 每帧间隔采集时间,毫秒
+        /// </summary>
+        public int Interval { get; set; }
+
+        /// <summary>
+        /// 图像目标区域
+        /// </summary>
+        public Rectangle RectROI { get; set; }
+        public int CurrExposure { get; set; }
+        /// <summary>
+        /// 当前状态:true=工作中,正在采集数据;false=空闲中,未采集数据;
+        /// </summary>
+        public bool IsWorking { get; set; }
+
+        /// <summary>
+        /// 采集到的色彩数据队列
+        /// </summary>
+        private List<ColorDataPoint> LstColorDataPoints;
+
+        /// <summary>
+        /// 新采样点采集事件
+        /// </summary>
+        public event EventHandler ColorDataGrabed;
+
+        /// <summary>
+        /// 在定位时不触发信号采集事件
+        /// </summary>
+        public bool IsNoFiredEvent { get; set; }
+
+        #endregion
+
+        #region methods
+
+        /// <summary>
+        /// 构造色点采集器对象
+        /// </summary>
+        /// <param name="webcam"></param>
+        public ColorDataGraber(WebCamera webcam)
+        {
+            WebCam = webcam;
+            ColorAnalysis = new ImageRGBanalysis();
+            RectROI = new Rectangle(0, 0, 10, 10);
+            LstColorDataPoints = new List<ColorDataPoint>();
+            Interval = 50;
+            IsStopGrab = false;
+            IsWorking = false;
+            IsNoFiredEvent = false;
+        }
+
+        /// <summary>
+        /// 定时拍照采集图像,在后台线程执行
+        /// </summary>
+        public void StartGrab()
+        {
+            LstColorDataPoints.Clear();
+            IsStopGrab = false;
+            if (WebCam == null)
+            {
+                return;
+            }
+            var thrGrab = new Thread(imageGrabing)
+            {
+                IsBackground = true
+            };
+            thrGrab.Start();
+        }
+
+        /// <summary>
+        /// 结束采集线程
+        /// </summary>
+        public void StopGrab()
+        {
+            if (logger != null)
+            {
+                logger.LogDebug("StopGrab()");
+            }
+            IsStopGrab = true;
+        }
+
+
+        /// <summary>
+        /// 自动调整目标取图区域的位置
+        /// </summary>
+        public Rectangle UpdateRoiPos(bool writeFlag)
+        {
+            try
+            {
+                Bitmap imgBitmap = WebCam.GrabImage();
+                if (imgBitmap == null)
+                {
+                    int cnt = 30;
+                    while (imgBitmap == null && cnt-- > 0)
+                    {
+                        Thread.Sleep(100);
+                        imgBitmap = WebCam.GrabImage();
+                    }
+                    if (imgBitmap == null)
+                    {
+                        return TitrationSettings.SensorPara.RoiSample;
+                    }
+                }
+                ColorAnalysis.IsLog = true;
+                logger?.LogInformation(string.Format("old roi=({0}, {1}, {2}, {3})", RectROI.X, RectROI.Y, RectROI.Right, RectROI.Bottom));
+                RectROI = ColorAnalysis.GetRoiPos(imgBitmap, TitrationSettings.SensorPara.RoiSample, 15); // 定位新的目标区域坐标
+                RectROI.Offset(TitrationSettings.SensorPara.RoiOffsetX, 0); // 加入偏移量
+                if (TitrationSettings.SensorPara.UseRoiMask)
+                {
+                    // 计算目标区域的均值,以均值的1/2作为阈值查找RoiMask
+                    int sumR = 0, sumG = 0, sumB = 0;
+                    ColorAnalysis.IsLog = false;
+                    ColorAnalysis.GetTotalFromImage(imgBitmap, RectROI, ref sumR, ref sumG, ref sumB);
+                    ColorAnalysis.IsLog = true;
+                    sumB = Math.Min(10, (sumB / (RectROI.Width * RectROI.Height)) / 2);
+                    #region 检查信号值是否异常[=0]
+
+#if false
+                    if (sumB < 0.5)
+                    {
+                        throw new Exception("信号值异常[0],滴定须终止。");
+                    }
+#endif
+
+                    #endregion
+                    TitrationSettings.SensorPara.RoiMaskPoints = ColorAnalysis.GetRoiMask(imgBitmap, RectROI, sumB);
+                }
+                if (logger != null)
+                {
+                    logger.LogInformation(string.Format("new roi=({0}, {1}, {2}, {3}), offset=({4},0)", RectROI.X, RectROI.Y,
+                        RectROI.Right, RectROI.Bottom, TitrationSettings.SensorPara.RoiOffsetX));
+                }
+                if (writeFlag)
+                {
+                    logger.LogInformation(ColorAnalysis.WriteRGBlog(imgBitmap, RectROI, "定位后数据" + RectROI.ToString()));
+                }
+            }
+            catch (Exception ex)
+            {
+                if (logger != null)
+                {
+                    logger.LogError(ex.ToString());
+                }
+            }
+            return RectROI;
+        }
+
+        /// <summary>
+        /// 自动调节曝光值
+        /// </summary>
+        public void PresetLightParam()
+        {
+            WebCam.SetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, -9, AForge.Video.DirectShow.CameraControlFlags.Manual);
+            WebCam.GetPropertiesRange(AForge.Video.DirectShow.CameraControlProperty.Exposure, out var min, out var max, out var size, out var value, out var flag);
+            Thread.Sleep(2000);
+            // 读取信号值
+            var cdp = GrabSignal(500);
+            // 判断信号所在区间,小于额定值范围则缩小曝光值,大于额定范围则增大曝光值
+            AForge.Video.DirectShow.CameraControlFlags flags = AForge.Video.DirectShow.CameraControlFlags.Manual;
+            WebCam.GetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, out int val, out flags);
+            int maxcount = 10;
+            while (maxcount > 0)
+            {
+                maxcount--;
+                if (cdp.GetBSV() < 60)
+                {
+                    if (val.Equals(min))
+                        break;
+                    val = Math.Min(max, Math.Max(-9, val - 1));
+                    WebCam.SetProperties(
+                        AForge.Video.DirectShow.CameraControlProperty.Exposure,
+                        val,
+                        AForge.Video.DirectShow.CameraControlFlags.Manual);
+                    Thread.Sleep(200);
+                    UpdateRoiPos(writeFlag: true);
+                }
+                else if (cdp.GetBSV() > 80)
+                {
+                    if (val.Equals(max)) break;
+                    val = Math.Min(max, Math.Max(-9, val + 1));
+                    WebCam.SetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, val, AForge.Video.DirectShow.CameraControlFlags.Manual);
+                    Thread.Sleep(500);
+                    UpdateRoiPos(writeFlag: true);
+                }
+                else
+                {
+                    break;
+                }
+                cdp = GrabSignal(500);
+            }
+            if (logger != null)
+            {
+                logger.LogInformation("new exposure:" + val.ToString());
+            }
+        }
+
+        public void PresetLightParammanual(int Brightness)
+        {
+            int maxRange = -9;//最大曝光
+            WebCam.SetProperties((CameraControlProperty)4, maxRange, (CameraControlFlags)2);
+            WebCam.GetPropertiesRange((CameraControlProperty)4, out var minValue, out var maxValue, out var _, out var _, out var _);
+            Thread.Sleep(1000);
+            ColorDataPoint colorDataPoint = GrabSignal(500);
+            CameraControlFlags flags2 = (CameraControlFlags)2;
+            WebCam.GetProperties((CameraControlProperty)4, out var val, out flags2);
+            int num = 10;
+            double num2 = 115.0;
+            double num3 = 115.0;
+            int num4 = -9;
+            int num5 = 0;
+            double num6 = 0.0;
+            bool flag = true;
+            while (num > 0)
+            {
+                num--;
+                num6 = colorDataPoint.GetBSV();
+                if (num6 < 110.0)
+                {
+                    num2 = num6;
+                    if (val.Equals(minValue))
+                    {
+                        break;
+                    }
+
+                    val = Math.Min(maxValue, Math.Max(maxRange, val - 1));
+                    num4 = val;
+                    ILogger obj = logger;
+                }
+                else
+                {
+                    if (!(num6 > 120.0))
+                    {
+                        flag = false;
+                        break;
+                    }
+
+                    num3 = num6;
+                    if (val.Equals(maxValue))
+                    {
+                        break;
+                    }
+
+                    val = Math.Min(maxValue, Math.Max(maxRange, val + 1));
+                    num5 = val;
+                }
+
+                WebCam.SetProperties((CameraControlProperty)4, val, (CameraControlFlags)2);
+                Thread.Sleep(500);
+                UpdateRoiPos(writeFlag: false);
+                colorDataPoint = GrabSignal(500);
+            }
+
+            if (flag)
+            {
+                val = ((115.0 - num2 > num3 - 115.0) ? num4 : num5);
+                val = Math.Min(maxValue, Math.Max(maxRange, val));
+                WebCam.SetProperties((CameraControlProperty)4, val, (CameraControlFlags)2);
+                Thread.Sleep(500);
+                ContinuePresetLightParam();
+            }
+            else
+            {
+                WebCam.GetProperties((CameraControlProperty)4, out val, out flags2);
+                logger.LogInformation($"**************曝光值为:" + val );
+            }
+        }
+
+        public void ContinuePresetLightParam()
+        {
+            //IL_0046: Unknown result type (might be due to invalid IL or missing references)
+            WebCam.GetVideoProcAmpPropertiesRange((VideoProcAmpProperty)0, out var minValue, out var maxValue, out var _, out var _, out var flags);
+            Thread.Sleep(1000);
+            ColorDataPoint colorDataPoint = GrabSignal(500);
+            VideoProcAmpFlags flags2 = (VideoProcAmpFlags)2;
+            WebCam.GetVideoProcAmpProperties((VideoProcAmpProperty)0, out var val, out flags2);
+            int num = 40;
+            while (num > 0)
+            {
+                num--;
+                if (colorDataPoint.GetBSV() < 110.0)
+                {
+                    if (val < minValue)
+                    {
+                        break;
+                    }
+
+                    val++;
+                    ILogger obj = logger;
+                }
+                else
+                {
+                    if (!(colorDataPoint.GetBSV() > 120.0) || val > maxValue)
+                    {
+                        break;
+                    }
+
+                    val--;
+                    ILogger obj2 = logger;
+                }
+
+                WebCam.SetVideoProcAmpProperties((VideoProcAmpProperty)0, val, (VideoProcAmpFlags)2);
+                Thread.Sleep(500);
+                UpdateRoiPos(writeFlag: false);
+                colorDataPoint = GrabSignal(500);
+            }
+
+            //if (colorDataPoint.GetBSV() > 120.0)
+            //{
+            //    PresetLightParammanual(cameraValueAll, 10);
+            //}
+
+            WebCam.GetVideoProcAmpProperties((VideoProcAmpProperty)0, out val, out flags);
+
+            logger.LogInformation($"**************曝光值和亮度为:"+ CurrExposure + "______"+ val.ToString());
+        }
+
+
+        public void SavePicture(string filename)
+        {
+            if (string.IsNullOrWhiteSpace(filename))
+            {
+                WebCam.GrabImage().Save(filename);
+            }
+        }
+
+        /// <summary>
+        /// 连续采集指定时长的信号值,计算并返回该时长的信号总值和均值
+        /// </summary>
+        /// <param name="milsecCount">指定采集时长</param>
+        /// <returns>信号统计数据</returns>
+        public ColorDataPoint GrabSignal(int milsecCount)
+        {
+            if (milsecCount < 35) milsecCount = 35;
+            IsWorking = true;
+            ColorDataPoint cdp;
+            var rectRoi = RectROI;
+            int vr = 0, vg = 0, vb = 0;
+            var area = rectRoi.Width * rectRoi.Height;
+            if (TitrationSettings.SensorPara.UseRoiMask && TitrationSettings.SensorPara.RoiMaskPoints is not null)
+            {
+                area = TitrationSettings.SensorPara.RoiMaskPoints.Count;
+            }
+            // 采集指定时长的数据,计算均值返回
+            LstColorDataPoints.Clear();
+            var timeLength = 0;
+            IsStopGrab = false;
+            var sw = new Stopwatch();
+            while (!IsStopGrab)
+            {
+                sw.Restart();
+                var bmp = WebCam.GrabImage();
+                if (TitrationSettings.SensorPara.UseRoiMask && TitrationSettings.SensorPara.RoiMaskPoints != null)
+                {
+                    ColorAnalysis.GetTotalFromImage(bmp, TitrationSettings.SensorPara.RoiMaskPoints, ref vr, ref vg, ref vb);
+                }
+                else
+                {
+                    ColorAnalysis.GetTotalFromImage(bmp, rectRoi, ref vr, ref vg, ref vb);
+                }
+
+                cdp = new ColorDataPoint { Id = LstColorDataPoints.Count, Area = area, RTV = vr, GTV = vg, BTV = vb };
+                LstColorDataPoints.Add(cdp); // 加入队列保存
+                // 固定每帧图像间隔
+                if (sw.ElapsedMilliseconds >= Interval)
+                {
+                    sw.Stop();
+                }
+                else
+                {
+                    Thread.Sleep(Interval - Convert.ToInt32(sw.ElapsedMilliseconds));
+                }
+                // 是否已到指定时长,结束采集
+                timeLength += Interval;
+                if (timeLength >= milsecCount)
+                {
+                    break;
+                }
+            }
+
+            var lstSort = LstColorDataPoints.OrderByDescending(c => c.GTV).ToList();
+            var nMedianPos = Convert.ToInt32(Math.Ceiling(lstSort.Count / 2.0));
+            cdp = lstSort[nMedianPos - 1];
+            if (logger != null && PrintDebugLog == true)
+            {
+                logger.LogInformation($"use a={cdp.GetRSV()},b={cdp.GetGSV()},c={cdp.GetBSV()},d={cdp.GetSaturation()}");
+            }
+            IsWorking = false;
+            Messager.Send("DropDoing", 300 - cdp.GetBSV());
+#if false
+            if (ColorDataGrabed != null && !IsNoFiredEvent)
+            {
+                ColorDataGrabed?.Invoke(this, new ColorDataGrabedEventArgs() { ColorPoint = cdp });
+            } // 发布采样点事件2
+#endif
+            return cdp;
+        }
+
+        /// <summary>
+        /// 根据启停指令连续采集信号值,计算最后的frameCount帧数据并返回信号总值和均值
+        /// </summary>
+        /// <param name="frameCount">参与计算的帧数</param>
+        /// <returns>信号统计数据</returns>
+        public ColorDataPoint GrabSignalWithFrame(int frameCount)
+        {
+            if (frameCount < 10)
+            {
+                frameCount = 10;
+            }
+
+            IsWorking = true;
+
+            Bitmap bmp;
+            ColorDataPoint cdp;
+            Rectangle rectROI = RectROI;
+            int vr = 0, vg = 0, vb = 0;
+            int area = rectROI.Width * rectROI.Height;
+            if (TitrationSettings.SensorPara.UseRoiMask && TitrationSettings.SensorPara.RoiMaskPoints != null)
+            {
+                area = TitrationSettings.SensorPara.RoiMaskPoints.Count;
+            }
+
+            // 采集指定时长的数据,计算均值返回
+            LstColorDataPoints.Clear();
+            int timelength = 0;
+            IsStopGrab = false;
+            Stopwatch sw = new Stopwatch();
+            while (!IsStopGrab)
+            {
+                sw.Restart();
+                bmp = WebCam.GrabImage(); // 拍照
+                if (TitrationSettings.SensorPara.UseRoiMask && TitrationSettings.SensorPara.RoiMaskPoints != null)
+                {
+                    ColorAnalysis.GetTotalFromImage(bmp, TitrationSettings.SensorPara.RoiMaskPoints, ref vr, ref vg,
+                        ref vb);
+                }
+                else
+                {
+                    ColorAnalysis.GetTotalFromImage(bmp, rectROI, ref vr, ref vg, ref vb);
+                }
+
+                cdp = new ColorDataPoint { Id = LstColorDataPoints.Count, Area = area, RTV = vr, GTV = vg, BTV = vb };
+                LstColorDataPoints.Add(cdp); // 加入队列保存
+                if (LstColorDataPoints.Count > frameCount)
+                {
+                    LstColorDataPoints.RemoveAt(0);
+                }
+
+                ////if (ColorDataGrabed != null) { ColorDataGrabed(this, new ColorDataGrabedEventArgs() { ColorPoint = cdp }); } // 发布采样点事件1
+                if (logger != null && PrintDebugLog == true)
+                {
+                    //  PublicDatas.LogProgram.Info(string.Format("a={0},b={1},c={2},d={3}", cdp.GetRSV(), cdp.GetGSV(), cdp.GetBSV(), cdp.GetSaturation()));
+                }
+
+                // 固定每帧图像间隔
+                if (sw.ElapsedMilliseconds >= Interval)
+                {
+                    sw.Stop();
+                }
+                else
+                {
+                    Thread.Sleep(Interval - Convert.ToInt32(sw.ElapsedMilliseconds));
+                }
+
+                // 是否已到指定时长,结束采集
+                timelength += Interval;
+                if (timelength >= frameCount)
+                {
+                    break;
+                }
+            }
+
+            var lstSort = LstColorDataPoints.OrderByDescending(c => c.GTV).ToList();
+            var nMedianPos = Convert.ToInt32(Math.Ceiling(lstSort.Count / 2.0));
+            cdp = lstSort[nMedianPos - 1];
+            if (logger != null && PrintDebugLog == true)
+            {
+                // LogProgram.Info(string.Format("use a={0},b={1},c={2},d={3}", cdp.GetRSV(), cdp.GetGSV(), cdp.GetBSV(), cdp.GetSaturation()));
+            }
+
+            IsWorking = false;
+            ColorDataGrabed?.Invoke(this, new ColorDataGrabedEventArgs() { ColorPoint = cdp });
+
+            return cdp;
+        }
+
+        #endregion
+
+        #region protected methods
+
+        /// <summary>
+        /// 固定间隔时间,循环采集图像
+        /// </summary>
+        protected void imageGrabing()
+        {
+            IsWorking = true;
+
+            Bitmap bmp;
+            ColorDataPoint cdp;
+            Rectangle rectROI = RectROI;
+            int vr = 0, vg = 0, vb = 0;
+            int area = rectROI.Width * rectROI.Height;
+            if (TitrationSettings.SensorPara.UseRoiMask && TitrationSettings.SensorPara.RoiMaskPoints != null)
+            {
+                if (logger != null)
+                {
+                    logger.LogDebug("UseRoiMaskPoints = True");
+                }
+
+                area = TitrationSettings.SensorPara.RoiMaskPoints.Count;
+            }
+
+            if (logger != null)
+            {
+                logger.LogDebug("area = " + area.ToString());
+            }
+
+            Stopwatch sw = new();
+            while (!IsStopGrab)
+            {
+                sw.Restart();
+                bmp = WebCam.GrabImage();
+
+                if (TitrationSettings.SensorPara.UseRoiMask && TitrationSettings.SensorPara.RoiMaskPoints != null)
+                {
+                    ColorAnalysis.GetTotalFromImage(bmp, TitrationSettings.SensorPara.RoiMaskPoints, ref vr, ref vg,
+                        ref vb);
+                }
+                else
+                {
+                    ColorAnalysis.GetTotalFromImage(bmp, rectROI, ref vr, ref vg, ref vb);
+                }
+
+                cdp = new ColorDataPoint { Id = LstColorDataPoints.Count, Area = area, RTV = vr, GTV = vg, BTV = vb };
+                LstColorDataPoints.Add(cdp); // 加入队列保存
+                if (ColorDataGrabed != null)
+                {
+                    ColorDataGrabed(this, new ColorDataGrabedEventArgs() { ColorPoint = cdp });
+                } // 发布采样点事件
+
+                if (logger != null && PrintDebugLog == true)
+                {
+                    logger.LogDebug(string.Format("a={0}, b={1}, c={2}", cdp.GetRSV(), cdp.GetGSV(), cdp.GetBSV()));
+                }
+
+                if (sw.ElapsedMilliseconds >= Interval)
+                {
+                    sw.Stop();
+                }
+                else
+                {
+                    Thread.Sleep(Interval - Convert.ToInt32(sw.ElapsedMilliseconds));
+                }
+            }
+
+            IsWorking = false;
+        }
+
+        #endregion
+    }
+}

+ 152 - 0
SHJX.Service.Common/Camera/ColorDataPoint.cs

@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SHJX.Service.Common.Camera
+{
+    public class ColorDataPoint
+    {
+        #region protected
+        /// <summary>
+        /// 单点颜色
+        /// </summary>
+        protected Color PointColor { get; set; }
+
+
+        protected void UpdatePoint()
+        {
+            PointColor = Color.FromArgb(
+                Convert.ToInt32(RTV / Area),
+                Convert.ToInt32(GTV / Area),
+                Convert.ToInt32(BTV / Area));
+        }
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// 序号
+        /// </summary>
+        public int Id { get; set; }
+
+        /// <summary>
+        /// 区域R总量
+        /// </summary>
+        public double RTV { get; set; }
+
+        /// <summary>
+        /// 区域G总量
+        /// </summary>
+        public double GTV { get; set; }
+
+        /// <summary>
+        /// 区域B总量
+        /// </summary>
+        public double BTV { get; set; }
+
+        /// <summary>
+        /// 区域面积
+        /// </summary>
+        public double Area { get; set; }
+
+        /// <summary>
+        /// 小数点后位数
+        /// </summary>
+        public int FloatCount { get; set; }
+
+        #endregion
+
+        #region methods
+        public ColorDataPoint(int id = 1, double rtv = 1, double gtv = 1, double btv = 1, double area = 1, int floatcount = 2)
+        {
+            Id = id;
+            RTV = rtv;
+            GTV = gtv;
+            BTV = btv;
+            Area = area;
+            FloatCount = floatcount;
+            UpdatePoint();
+        }
+
+        /// <summary>
+        /// 返回单点R平均值
+        /// </summary>
+        /// <returns></returns>
+        public double GetRSV()
+        {
+            double ret = Math.Round(RTV / Area, FloatCount);
+            return ret;
+        }
+
+        /// <summary>
+        /// 返回单点G平均值
+        /// </summary>
+        /// <returns></returns>
+        public double GetGSV()
+        {
+            double ret = Math.Round(GTV / Area, FloatCount);
+            return ret;
+        }
+
+        /// <summary>
+        /// 返回单点B平均值
+        /// </summary>
+        /// <returns></returns>
+        public double GetBSV()
+        {
+            double ret = Math.Round(BTV / Area, FloatCount);
+            return ret;
+        }
+
+        /// <summary>
+        /// 返回HSB色调值
+        /// </summary>
+        /// <returns></returns>
+        public double GetHue()
+        {
+            this.UpdatePoint();
+            return Math.Round(this.PointColor.GetHue(), FloatCount);
+        }
+
+        /// <summary>
+        /// 返回HSB饱和度值
+        /// </summary>
+        /// <returns></returns>
+        public double GetSaturation()
+        {
+            return 1.0;
+            #region MyRegion
+
+//#if true
+//            UpdatePoint();
+//            return Math.Round(this.PointColor.GetSaturation(), FloatCount);
+//#endif
+
+            //#region MyRegion
+
+            //this.UpdatePoint();
+            //var vmax = Math.Max(this.GTV, Math.Max(this.RTV, this.BTV)) / this.Area;
+            //var vmin = Math.Min(this.GTV, Math.Min(this.RTV, this.BTV)) / this.Area;
+            //var sat = Math.Max(1.0, Math.Round((vmax - vmin) / vmax * 100.0, 2));
+            //return sat;
+
+            //#endregion
+            #endregion   
+        }
+
+        /// <summary>
+        /// 返回HSB亮度值
+        /// </summary>
+        /// <returns></returns>
+        public double GetBrightness()
+        {
+            UpdatePoint();
+            return Math.Round(this.PointColor.GetBrightness(), FloatCount);
+        }
+        #endregion
+
+    }
+}

+ 57 - 0
SHJX.Service.Common/Camera/CreatGraber.cs

@@ -0,0 +1,57 @@
+
+using System.Collections.Generic;
+using SHJX.Service.Common.ReadXML;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+namespace SHJX.Service.Common.Camera
+{
+    public class CreatGraber
+    {
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(CreatGraber));
+        /// <summary>
+        /// 摄像头
+        /// </summary>
+        public WebCamera WebCam { get; set; }
+        public ColorDataGraber Graber { get; set; }
+        /// <summary>
+        /// 是否检测到摄像头
+        /// </summary>
+        public bool HadWebcams { get; set; }
+
+        public  CreatGraber(string LogiCameraName, out int cameraNum)
+        {
+            cameraNum = 0;
+            TitrationSettings.SensorPara = new SensorsParameter();
+            #region 检测识别系统中的探头设备
+            HadWebcams = false;
+            for (var i = 0; i < WebCamera.GetCameraCounts(); i++)
+            {
+                // 部分型号摄像头名称包含特殊符号,难以输入,故修改为名称关键词识别匹配,且兼容之前版本
+                if (!WebCamera.GetCameraName(i).ToLower().Contains(TitrationSettings.SensorPara.CamSensorName.ToLower()) )
+                    continue;
+                WebCam = new WebCamera(WebCamera.GetCameraMoniker(i));
+                string camName = WebCam.vidCapDevice.Source.Split('&')[3];
+                logger.LogInformation("相机编号为"+ camName + ",请配写入配置文件中");
+                if (LogiCameraName.Equals( camName))
+                {
+                    HadWebcams = true;
+                    cameraNum = 1;
+                    break;
+                }
+            }
+            if (WebCam is null) 
+            { 
+                logger.LogError("未找到摄像头!"); 
+            }
+            
+            WebCam ??= new WebCamera();
+            Graber = new ColorDataGraber(WebCam)
+            {
+                Interval = 40,
+                RectROI = TitrationSettings.SensorPara.RoiSample
+            };
+            #endregion
+        
+        }
+    }
+}

+ 640 - 0
SHJX.Service.Common/Camera/ImageRGBanalysis.cs

@@ -0,0 +1,640 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+
+namespace SHJX.Service.Common.Camera
+{
+    public class ImageRGBanalysis
+    {
+        public ImageRGBanalysis()
+        {
+            IsLog = false;
+        }
+
+        #region properties
+
+        /// <summary>
+        /// 是否写Log
+        /// </summary>
+        public bool IsLog { get; set; }
+
+        #endregion
+
+        #region methods
+        public Bitmap CropImage(Bitmap srcImage, Rectangle rcCrop)
+        {
+            BitmapData bmpData = srcImage.LockBits(rcCrop, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+            Bitmap dstImage = new(rcCrop.Width, rcCrop.Height, bmpData.Stride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, bmpData.Scan0);
+            srcImage.UnlockBits(bmpData);
+
+            return dstImage;
+        }
+        public int GetTotalFromImage(Bitmap srcImage, Rectangle rcCrop, string strChannel = "G")
+        {
+            int channel = 0; // 要提取的颜色通道
+            channel = strChannel.ToUpper() switch
+            {
+                "B" => 0,
+                "G" => 1,
+                "R" => 2,
+                _ => 0,
+            };
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return 0; // 暂时只处理24bit图像
+            }
+            BitmapData bmpData = srcImage.LockBits(rcCrop, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+
+            int totalVal = 0; // 用于统计指定颜色总和
+            unsafe
+            {
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = 0; i < bmpData.Height; i++)
+                {
+                    for (int j = 0; j < bmpData.Width; j++)
+                    {
+                        totalVal += *(pSrc + bmpData.Stride * i + j * 3 + channel);
+                    }
+                }
+            }
+
+            srcImage.UnlockBits(bmpData);
+
+            return totalVal;
+        }
+
+        /// <summary>
+        /// 写图像指定区域的RGB值到日志文件中
+        /// </summary>
+        /// <param name="srcImage">图像像素</param>
+        /// <param name="rcCrop">处理区域</param>
+        /// <param name="title">日志标题</param>
+        public string WriteRGBlog(Bitmap srcImage, Rectangle rcCrop, string title)
+        {
+            if (srcImage == null ||
+                srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return string.Empty; // 暂时只处理24bit图像
+            }
+            BitmapData bmpData = srcImage.LockBits(rcCrop, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+            int vr = 0, vg = 0, vb = 0; // 单个像素点
+            string strr = "A:\r\n";
+            string strg = "B:\r\n";
+            string strb = "C:\r\n";
+            unsafe
+            {
+                List<Color> img = new(); // 用于过滤的像素数据
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = 0; i < bmpData.Height; i++)
+                {
+                    for (int j = 0; j < bmpData.Width; j++)
+                    {
+                        vb = *(pSrc + bmpData.Stride * i + j * 3 + 0);
+                        vg = *(pSrc + bmpData.Stride * i + j * 3 + 1);
+                        vr = *(pSrc + bmpData.Stride * i + j * 3 + 2);
+
+                        strr = string.Format("{0}{1},", strr, vr.ToString().PadLeft(3, ' '));
+                        strg = string.Format("{0}{1},", strg, vg.ToString().PadLeft(3, ' '));
+                        strb = string.Format("{0}{1},", strb, vb.ToString().PadLeft(3, ' '));
+                    }
+                    strr = string.Format("{0}\r\n", strr);
+                    strg = string.Format("{0}\r\n", strg);
+                    strb = string.Format("{0}\r\n", strb);
+                }
+            }
+            srcImage.UnlockBits(bmpData);
+            return strb;
+        }
+
+        /// <summary>
+        /// 在左右偏移范围内重新定位目标区域的坐标位置:连续4点相减都满足小于阈值
+        /// </summary>
+        /// <param name="srcImage">图像像素数据</param>
+        /// <param name="rcCrop">目标区域</param>
+        /// <param name="offset">左右偏移查找范围</param>
+        /// <param name="threhold">连续两点之差阈值</param>
+        /// <returns></returns>
+        public Rectangle GetRoiPos2(Bitmap srcImage, Rectangle rcCrop, int offset = 10, int threhold = 3)
+        {
+            Rectangle newRoi = new(rcCrop.Location, rcCrop.Size);
+
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return newRoi; // 暂时只处理24bit图像
+            }
+
+            BitmapData bmpData = srcImage.LockBits(new Rectangle(0, 0, srcImage.Width, srcImage.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+            int vg1 = 0, vg2 = 0, vg3, vg4 = 0;//, vg5 = 0;
+            int rowno = (rcCrop.Top + rcCrop.Bottom) / 2;
+            int left = Math.Max(0, (rcCrop.Left - offset));
+            int right = Math.Min(srcImage.Width - 1, (rcCrop.Right + offset));
+            unsafe
+            {
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int j = left; j < right; j++)
+                {
+                    int istart = bmpData.Stride * rowno + j * 3 + 1;
+                    vg1 = *(pSrc + istart + 3 * 0);
+                    vg2 = *(pSrc + istart + 3 * 1);
+                    vg3 = *(pSrc + istart + 3 * 2);
+                    vg4 = *(pSrc + istart + 3 * 3);
+                    //vg5 = *(pSrc + istart + 3 * 4);
+                    if (vg2 - vg1 >= threhold &&
+                        vg3 - vg2 >= threhold &&
+                        vg4 - vg3 >= threhold)
+                    {
+                        left = j;
+                        break;
+                    }
+                }
+                for (int j = right; j > left; j--)
+                {
+                    int istart = bmpData.Stride * rowno + j * 3 + 1;
+                    vg1 = *(pSrc + istart - 3 * 0);
+                    vg2 = *(pSrc + istart - 3 * 1);
+                    vg3 = *(pSrc + istart - 3 * 2);
+                    vg4 = *(pSrc + istart - 3 * 3);
+                    //vg5 = *(pSrc + istart - 3 * 4);
+                    if (vg2 - vg1 >= threhold &&
+                        vg3 - vg2 >= threhold &&
+                        vg4 - vg3 >= threhold)
+                    {
+                        right = j;
+                        break;
+                    }
+                }
+            }
+            srcImage.UnlockBits(bmpData);
+
+            newRoi.X = ((left + right) - rcCrop.Width) / 2 + 1;
+            //PublicDatas.LogProgram.Info(string.Format("found roi left={0}, right={1}", left, right));
+
+            return newRoi;
+        }
+
+        /// <summary>
+        /// 以指定值threhold为阈值,找到上下左右边界,然后以此为中心返回新的目标矩形区域
+        /// </summary>
+        /// <param name="srcImage"></param>
+        /// <param name="rcCrop"></param>
+        /// <param name="offset"></param>
+        /// <param name="threhold"></param>
+        /// <returns></returns>
+        public Rectangle GetRoiPos(Bitmap srcImage, Rectangle rcCrop, int offset, int threhold)
+        {
+            Rectangle newRoi = new(rcCrop.Location, rcCrop.Size);
+
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return newRoi; // 暂时只处理24bit图像
+            }
+
+            BitmapData bmpData = srcImage.LockBits(new Rectangle(0, 0, srcImage.Width, srcImage.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+            int vg1 = 0;//, vg2 = 0, vg3, vg4 = 0, vg5 = 0;
+            int top = Math.Max(0, rcCrop.Top - offset);
+            int bottom = Math.Min(srcImage.Height - 1, rcCrop.Bottom + offset);
+            int left = Math.Max(0, (rcCrop.Left - offset));
+            int right = Math.Min(srcImage.Width - 1, (rcCrop.Right + offset));
+            unsafe
+            {
+                int pos = 0;
+                // 上边界
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = top; i <= bottom; i++)
+                {
+                    for (int j = left; j <= right; j++)
+                    {
+                        pos = bmpData.Stride * i + j * 3 + 1;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            top = i; i = bottom + 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+                // 下边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = bottom; i >= top; i--)
+                {
+                    for (int j = left; j <= right; j++)
+                    {
+                        pos = bmpData.Stride * i + j * 3 + 1;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            bottom = i; i = top - 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+                // 左边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = left; i <= right; i++)
+                {
+                    for (int j = top; j <= bottom; j++)
+                    {
+                        pos = bmpData.Stride * j + i * 3 + 1;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            left = i; i = right + 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+                // 右边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = right; i >= left; i--)
+                {
+                    for (int j = top; j <= bottom; j++)
+                    {
+                        pos = bmpData.Stride * j + i * 3 + 1;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            right = i; i = left - 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+            }
+            srcImage.UnlockBits(bmpData);
+
+            newRoi.X = ((left + right) - rcCrop.Width) / 2 + 1;
+            newRoi.Y = ((top + bottom) - rcCrop.Height) / 2 + 1;
+            //PublicDatas.LogProgram.Info(string.Format("found roi left={0}, right={1}, top = {2}, bottom = {3}", left, right, top, bottom));
+
+            return newRoi;
+        }
+
+        /// <summary>
+        /// 以图像最大值的1/2作为阈值,找到上下左右边界,然后以此为中心返回新的目标矩形区域
+        /// </summary>
+        /// <param name="srcImage"></param>
+        /// <param name="rcCrop"></param>
+        /// <param name="offset"></param>
+        /// <returns></returns>
+        public Rectangle GetRoiPos(Bitmap srcImage, Rectangle rcCrop, int offset)
+        {
+            Rectangle newRoi = new(rcCrop.Location, rcCrop.Size);
+
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return newRoi; // 暂时只处理24bit图像
+            }
+
+            BitmapData bmpData = srcImage.LockBits(new Rectangle(0, 0, srcImage.Width, srcImage.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+            int vg1 = 0;
+            int top = Math.Max(0, rcCrop.Top - offset);
+            int bottom = Math.Min(srcImage.Height - 1, rcCrop.Bottom + offset);
+            int left = Math.Max(0, (rcCrop.Left - offset));
+            int right = Math.Min(srcImage.Width - 1, (rcCrop.Right + offset));
+            unsafe
+            {
+                int pos = 0;
+                // 找出最大值的1/2作为阈值
+                double threhold = 0;
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = top; i <= bottom; i++)
+                {
+                    for (int j = left; j <= right; j++)
+                    {
+                        pos = bmpData.Stride * i + j * 3 + 0;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            threhold = vg1;
+                        }
+                    }
+                }
+                if (threhold > 0) { threhold /= 2.0; }
+                else { threhold = 10; }
+                //PublicDatas.LogProgram.Debug("自动定位,定位阈值=" + threhold.ToString("F1"));
+
+                // 上边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = top; i <= bottom; i++)
+                {
+                    for (int j = left; j <= right; j++)
+                    {
+                        pos = bmpData.Stride * i + j * 3 + 0;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            top = i; i = bottom + 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+                // 下边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = bottom; i >= top; i--)
+                {
+                    for (int j = left; j <= right; j++)
+                    {
+                        pos = bmpData.Stride * i + j * 3 + 0;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            bottom = i; i = top - 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+                // 左边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = left; i <= right; i++)
+                {
+                    for (int j = top; j <= bottom; j++)
+                    {
+                        pos = bmpData.Stride * j + i * 3 + 0;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            left = i; i = right + 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+                // 右边界
+                pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = right; i >= left; i--)
+                {
+                    for (int j = top; j <= bottom; j++)
+                    {
+                        pos = bmpData.Stride * j + i * 3 + 0;
+                        vg1 = *(pSrc + pos);
+                        if (vg1 >= threhold)
+                        {
+                            right = i; i = left - 1; // 找到边界,终止循环
+                            break;
+                        }
+                    }
+                }
+            }
+            srcImage.UnlockBits(bmpData);
+
+            newRoi.X = ((left + right) - rcCrop.Width) / 2 + 1;
+            newRoi.Y = ((top + bottom) - rcCrop.Height) / 2 + 1;
+            //PublicDatas.LogProgram.Info(string.Format("found roi left={0}, right={1}, top = {2}, bottom = {3}", left, right, top, bottom));
+
+            return newRoi;
+        }
+
+        /// <summary>
+        /// 在指定的矩形区域内查找大于阈值的像素点的坐标点
+        /// </summary>
+        /// <param name="srcImage">图像像素数据</param>
+        /// <param name="rcCrop">指定矩形区域</param>
+        /// <param name="threholdVal">G值的阈值</param>
+        /// <returns>所有满足要求的坐标点</returns>
+        public List<Point> GetRoiMask(Bitmap srcImage, Rectangle rcCrop, double threholdVal = 20)
+        {
+            List<Point> roiMask = new();
+
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return null; // 暂时只处理24bit图像
+            }
+
+            BitmapData bmpData = srcImage.LockBits(rcCrop, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+            int vb = 0; // 单个像素点 vg = 0, vr = 0, 
+            unsafe
+            {
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = 0; i < bmpData.Height; i++)
+                {
+                    for (int j = 0; j < bmpData.Width; j++)
+                    {
+                        vb = *(pSrc + bmpData.Stride * i + j * 3 + 0); // 0
+                        //vg = *(pSrc + bmpData.Stride * i + j * 3 + 1); // 1
+                        //vr = *(pSrc + bmpData.Stride * i + j * 3 + 2); // 2
+
+                        if (vb >= threholdVal)
+                        {
+                            roiMask.Add(new Point(rcCrop.Left + j, rcCrop.Top + i));
+                        }
+                    }
+                }
+            }
+            srcImage.UnlockBits(bmpData);
+
+            return roiMask;
+        }
+
+        /// <summary>
+        /// 统计指定图像区域的rgb总值
+        /// </summary>
+        /// <param name="srcImage"></param>
+        /// <param name="rcCrop"></param>
+        /// <param name="sumR"></param>
+        /// <param name="sumG"></param>
+        /// <param name="sumB"></param>
+        /// <param name="noiseFilter">是否使用噪点过滤</param>
+        /// <returns></returns>
+        public bool GetTotalFromImage(Bitmap srcImage, Rectangle rcCrop, ref int sumR, ref int sumG, ref int sumB, NoiseDataFilter noiseFilter = null)
+        {
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return false; // 暂时只处理24bit图像
+            }
+            BitmapData bmpData = srcImage.LockBits(rcCrop, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
+
+            sumR = sumG = sumB = 0; // 指定颜色总和
+            int vr = 0, vg = 0, vb = 0; // 单个像素点
+            unsafe
+            {
+                int logR = 0, logG = 0, logB = 0; // 统计原始数据的总值,用于写日志
+                string strr = "R:\r\n";
+                string strg = "G:\r\n";
+                string strb = "B:\r\n";
+                List<Color> img = new(); // 用于过滤的像素数据
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                for (int i = 0; i < bmpData.Height; i++)
+                {
+                    for (int j = 0; j < bmpData.Width; j++)
+                    {
+                        vb = *(pSrc + bmpData.Stride * i + j * 3 + 0);
+                        vg = *(pSrc + bmpData.Stride * i + j * 3 + 1);
+                        vr = *(pSrc + bmpData.Stride * i + j * 3 + 2);
+
+                        logB += vb;
+                        logG += vg;
+                        logR += vr;
+
+                        if (noiseFilter == null)
+                        {
+                            sumB += vb;
+                            sumG += vg;
+                            sumR += vr;
+                        }
+                        else
+                        {
+                            img.Add(Color.FromArgb(vr, vg, vb));
+                        }
+
+                        if (IsLog)
+                        {
+                            strr = string.Format("{0}{1},", strr, vr.ToString().PadLeft(3, ' '));
+                            strg = string.Format("{0}{1},", strg, vg.ToString().PadLeft(3, ' '));
+                            strb = string.Format("{0}{1},", strb, vb.ToString().PadLeft(3, ' '));
+                        }
+                    }
+                    if (IsLog)
+                    {
+                        strr = string.Format("{0}\r\n", strr);
+                        strg = string.Format("{0}\r\n", strg);
+                        strb = string.Format("{0}\r\n", strb);
+                    }
+                }
+
+                #region 将rgb值写入日志
+                if (this.IsLog)
+                {
+                    try
+                    {
+                        string filename = @"d:\log\rgbdata_" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
+                        filename = @"d:\log\rgbdata_" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
+                        StreamWriter sw = new(filename, true, System.Text.Encoding.Default);
+                        sw.WriteLine(DateTime.Now.ToString("yyyyMMdd hh:mm:ss.fff") + " 原始数据:");
+                        //sw.WriteLine(strr);
+                        sw.WriteLine(strg);
+                        //sw.WriteLine(strb);
+                        sw.WriteLine();
+
+                        if (noiseFilter != null)
+                        {
+                            // 写日志
+                            noiseFilter.IsLog = true;
+                            noiseFilter.swLog = sw;
+                            noiseFilter.LogWidth = bmpData.Width;
+                            noiseFilter.CalcImage(img, ref sumR, ref sumG, ref sumB);
+                        }
+
+                        int tmp = bmpData.Width * bmpData.Height;
+                        strr = string.Format("原始总值:(R, G, B)({0}, {1}, {2}), 数据个数:{3}, ", logR, logG, logB, tmp)
+                            + string.Format("均值:(R, G, B)({0}, {1}, {2})\r\n", (logR * 1.0 / tmp).ToString("F2"), (logG * 1.0 / tmp).ToString("F2"), (logB * 1.0 / tmp).ToString("F2"));
+                        sw.WriteLine(strr);
+                        sw.WriteLine();
+
+                        sw.Close();
+                    }
+                    catch (Exception) { }
+                }
+                else
+                {
+                    // 不用写日志
+                    noiseFilter?.CalcImage(img, ref sumR, ref sumG, ref sumB);
+                }
+                #endregion
+            }
+
+            srcImage.UnlockBits(bmpData);
+
+            return true;
+        }
+
+        /// <summary>
+        /// 根据指定点的坐标队列,计算总值
+        /// </summary>
+        /// <param name="srcImage"></param>
+        /// <param name="roiPoint">计算点的坐标队列</param>
+        /// <param name="sumR"></param>
+        /// <param name="sumG"></param>
+        /// <param name="sumB"></param>
+        /// <param name="noiseFilter"></param>
+        /// <returns></returns>
+        public bool GetTotalFromImage(Bitmap srcImage, List<Point> roiPoint, ref int sumR, ref int sumG, ref int sumB, NoiseDataFilter noiseFilter = null)
+        {
+            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
+            {
+                return false; // 暂时只处理24bit图像
+            }
+            BitmapData bmpData = srcImage.LockBits(new Rectangle(0, 0, srcImage.Width, srcImage.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+
+            sumR = sumG = sumB = 0; // 指定颜色总和
+            int vr = 0, vg = 0, vb = 0; // 单个像素点
+            unsafe
+            {
+                int logR = 0, logG = 0, logB = 0; // 统计原始数据的总值,用于写日志
+                //string strr = "R:\r\n";
+                //string strg = "G:\r\n";
+                //string strb = "B:\r\n";
+                List<Color> img = new(); // 用于过滤的像素数据
+                byte* pSrc = (byte*)(void*)bmpData.Scan0;
+                foreach (Point pt in roiPoint)
+                {
+                    vb = *(pSrc + bmpData.Stride * pt.Y + pt.X * 3 + 0);
+                    vg = *(pSrc + bmpData.Stride * pt.Y + pt.X * 3 + 1);
+                    vr = *(pSrc + bmpData.Stride * pt.Y + pt.X * 3 + 2);
+                    logB += vb;
+                    logG += vg;
+                    logR += vr;
+                    if (noiseFilter == null)
+                    {
+                        sumB += vb;
+                        sumG += vg;
+                        sumR += vr;
+                    }
+                    else
+                    {
+                        img.Add(Color.FromArgb(vr, vg, vb));
+                    }
+                }
+
+                #region 将rgb值写入日志
+                /*if (IsLog)
+                {
+                    try
+                    {
+                        string filename = @"d:\log\rgbdata_" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
+                        filename = @"d:\log\rgbdata_" + DateTime.Now.ToString("yyyyMMdd") + ".txt";
+                        StreamWriter sw = new StreamWriter(filename, true, System.Text.Encoding.Default);
+                        sw.WriteLine(DateTime.Now.ToString("yyyyMMdd hh:mm:ss.fff") + " 原始数据:");
+                        sw.WriteLine(strr);
+                        sw.WriteLine(strg);
+                        sw.WriteLine(strb);
+                        sw.WriteLine();
+
+                        if (noiseFilter != null)
+                        {
+                            // 写日志
+                            noiseFilter.IsLog = true;
+                            noiseFilter.swLog = sw;
+                            noiseFilter.LogWidth = bmpData.Width;
+                            noiseFilter.CalcImage(img, ref sumR, ref sumG, ref sumB);
+                        }
+
+                        int tmp = bmpData.Width * bmpData.Height;
+                        strr = string.Format("原始总值:(R, G, B)({0}, {1}, {2}), 数据个数:{3}, ", logR, logG, logB, tmp)
+                            + string.Format("均值:(R, G, B)({0}, {1}, {2})\r\n", (logR * 1.0 / tmp).ToString("F2"), (logG * 1.0 / tmp).ToString("F2"), (logB * 1.0 / tmp).ToString("F2"));
+                        sw.WriteLine(strr);
+                        sw.WriteLine();
+
+                        sw.Close();
+                    }
+                    catch (Exception) 
+                    {
+                    }
+                }
+                else
+                {
+                    // 不用写日志
+                    if (noiseFilter != null)
+                    {
+                        noiseFilter.CalcImage(img, ref sumR, ref sumG, ref sumB);
+                    }
+                }*/
+                #endregion
+            }
+
+            srcImage.UnlockBits(bmpData);
+
+            return true;
+        }
+        #endregion
+    }
+}

+ 579 - 0
SHJX.Service.Common/Camera/NoiseDataFilter.cs

@@ -0,0 +1,579 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+
+namespace SHJX.Service.Common.Camera
+{
+    /// <summary>
+    /// 将队列中的前后两幅图像像素点进行相减,最大的10%和最小的10%视为噪点,其余值进行平均值计算
+    /// </summary>
+    public class NoiseDataFilter
+    {
+        public NoiseDataFilter()
+        {
+            this.Count = 2;
+            this.lstImages = new List<List<Color>>();
+            this.IsLog = false;
+            this.LogWidth = 12;
+            this.NoiseCnt = 10;
+        }
+
+        #region fields
+
+        /// <summary>
+        /// 队列中保留图像数,默认为2
+        /// </summary>
+        public int Count { get; set; }
+
+        /// <summary>
+        /// 过滤噪点数,单向
+        /// </summary>
+        public int NoiseCnt { get; set; }
+
+        /// <summary>
+        /// 是否写日志
+        /// </summary>
+        public bool IsLog { get; set; }
+
+        /// <summary>
+        /// 日志写入流
+        /// </summary>
+        public StreamWriter swLog { get; set; }
+
+        /// <summary>
+        /// 日志数据一行个数
+        /// </summary>
+        public int LogWidth { get; set; }
+
+        /// <summary>
+        /// 用于比较的图像像素队列
+        /// </summary>
+        protected List<List<Color>> lstImages { get; set; }
+
+        #endregion
+
+
+        #region methods
+
+        /// <summary>
+        /// 加入一幅图像到队列,不做处理
+        /// </summary>
+        /// <param name="newImage"></param>
+        public void AddImage(List<Color> newImage)
+        {
+            this.lstImages.Add(newImage);
+            while (this.lstImages.Count > this.Count)
+            {
+                this.lstImages.RemoveAt(0);
+            }
+        }
+
+        /// <summary>
+        /// 对图像进行噪点过滤,然后计算rgb总值
+        /// </summary>
+        /// <param name="newImage"></param>
+        /// <param name="sumR"></param>
+        /// <param name="sumG"></param>
+        /// <param name="sumB"></param>
+        public void CalcImage(List<Color> newImage, ref int sumR, ref int sumG, ref int sumB)
+        {
+            this.AddImage(newImage);
+            this.calcAvg(ref sumR, ref sumG, ref sumB);
+        }
+
+        /// <summary>
+        /// 清除队列数据
+        /// </summary>
+        public void Clear()
+        {
+            if (this.lstImages != null)
+            {
+                this.lstImages.Clear();
+            }
+        }
+
+        #endregion
+
+
+        #region protected methods
+
+        /// <summary>
+        /// 计算后图减前图的过滤图的总值
+        /// </summary>
+        /// <param name="sumR"></param>
+        /// <param name="sumG"></param>
+        /// <param name="sumB"></param>
+        protected void calcAvg1(ref int sumR, ref int sumG, ref int sumB)
+        {
+            if (this.lstImages == null)
+            {
+                throw new Exception("队列没有数据");
+            }
+
+            if (this.lstImages.Count >= 2)
+            {
+                // 计算滤噪后的总值
+                List<Color> img1 = this.lstImages[this.lstImages.Count - 2]; // 上一幅
+                List<Color> img2 = this.lstImages[this.lstImages.Count - 1]; // 当前幅
+
+                #region 计算后图减前图的差值
+                string strr = "R:\r\n", strg = "G:\r\n", strb = "B:\r\n";
+                List<IntRGB> imgsub = new List<IntRGB>();
+                IntRGB val;
+                for (int i = 0; i < img2.Count; i++)
+                {
+                    val = new IntRGB(
+                        img2[i].R - img1[i].R,
+                        img2[i].G - img1[i].G,
+                        img2[i].B - img1[i].B);
+                    imgsub.Add(val);
+
+                    if (this.IsLog)
+                    {
+                        strr = string.Format("{0}{1},", strr, val.R.ToString().PadLeft(3, ' '));
+                        strg = string.Format("{0}{1},", strg, val.G.ToString().PadLeft(3, ' '));
+                        strb = string.Format("{0}{1},", strb, val.B.ToString().PadLeft(3, ' '));
+                        if (i % this.LogWidth == 11)
+                        {
+                            strr = string.Format("{0}\r\n", strr);
+                            strg = string.Format("{0}\r\n", strg);
+                            strb = string.Format("{0}\r\n", strb);
+                        }
+                    }
+                }
+                if (this.IsLog)
+                {
+                    //strr = string.Format("{0}\r\n", strr);
+                    //strg = string.Format("{0}\r\n", strg);
+                    //strb = string.Format("{0}\r\n", strb);
+
+                    this.swLog.WriteLine("相减数据:");
+                    this.swLog.WriteLine(strr);
+                    this.swLog.WriteLine(strg);
+                    this.swLog.WriteLine(strb);
+                    this.swLog.WriteLine();
+                }
+                #endregion
+
+                #region 统计差值,以G值为准
+                Dictionary<int, int> dicimg = new Dictionary<int, int>();
+                foreach (IntRGB subval in imgsub)
+                {
+                    if (dicimg.Keys.Contains(subval.G))
+                    {
+                        dicimg[subval.G] += 1;
+                    }
+                    else
+                    {
+                        dicimg.Add(subval.G, 1);
+                    }
+                }
+                List<KeyValuePair<int, int>> lstsub = dicimg.OrderByDescending(c => c.Key).ToList();
+                #endregion
+
+                #region 计算阈值参数
+                NoiseParam npm = new NoiseParam(this.NoiseCnt);
+                npm.Threhold = img2.Count / 10;
+                int tmp = 0; // 计算上阈值
+                for (int i = 0; i < lstsub.Count; i++)
+                {
+                    tmp += lstsub[i].Value;
+                    if (tmp < npm.Threhold)
+                    {
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点+:" + lstsub[i].Key.ToString() + "[" + lstsub[i].Value.ToString() + "点]"); }
+                        continue; // 未够数,继续下一组
+                    }
+                    else if (tmp == npm.Threhold)
+                    {
+                        // 正好够数,记录阈值,退出循环
+                        npm.Upper = lstsub[i].Key;
+                        npm.UpCnt = lstsub[i].Value; // 该阈值需消除的点数
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点+:" + lstsub[i].Key.ToString() + "[" + npm.UpCnt.ToString() + "点]"); }
+                        break;
+                    }
+                    else
+                    {
+                        // 过数,计算阈值,退出循环
+                        npm.Upper = lstsub[i].Key;
+                        npm.UpCnt = lstsub[i].Value - (tmp - npm.Threhold);
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点+:" + lstsub[i].Key.ToString() + "[" + npm.UpCnt.ToString() + "点]"); }
+                        break;
+                    }
+                }
+                tmp = 0; // 计算下阈值
+                for (int i = lstsub.Count - 1; i >= 0; i--)
+                {
+                    tmp += lstsub[i].Value;
+                    if (tmp < npm.Threhold)
+                    {
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点-:" + lstsub[i].Key.ToString() + "[" + lstsub[i].Value.ToString() + "点]"); }
+                        continue; // 未够数,继续下一组
+                    }
+                    else if (tmp == npm.Threhold)
+                    {
+                        // 正好够数,记录阈值,退出循环
+                        npm.Lower = lstsub[i].Key;
+                        npm.LowCnt = lstsub[i].Value; // 该阈值需消除的点数
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点-:" + lstsub[i].Key.ToString() + "[" + npm.LowCnt.ToString() + "点]"); }
+                        break;
+                    }
+                    else
+                    {
+                        // 过数,计算阈值,退出循环
+                        npm.Lower = lstsub[i].Key;
+                        npm.LowCnt = lstsub[i].Value - (tmp - npm.Threhold);
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点-:" + lstsub[i].Key.ToString() + "[" + npm.LowCnt.ToString() + "点]"); }
+                        break;
+                    }
+                }
+                #endregion
+
+                #region 滤除噪点后,计算总值
+                for (int i = 0; i < img2.Count; i++)
+                {
+                    if (imgsub[i].G > npm.Upper || imgsub[i].G < npm.Lower)
+                    {
+                        continue; // 噪点,滤去
+                    }
+                    else if (imgsub[i].G == npm.Upper && npm.UpCnt > 0)
+                    {
+                        // 部分噪点,滤去部分
+                        npm.UpCnt--;
+                        continue;
+                    }
+                    else if (imgsub[i].G == npm.Lower && npm.LowCnt > 0)
+                    {
+                        // 部分噪点,滤去部分
+                        npm.LowCnt--;
+                        continue;
+                    }
+                    else
+                    {
+                        // 非噪点,计算总值
+                        sumR += img2[i].R;
+                        sumG += img2[i].G;
+                        sumB += img2[i].B;
+                    }
+                }
+                if (this.IsLog)
+                {
+                    tmp = img2.Count - npm.Threhold * 2;
+                    strr = string.Format("总值:(R, G, B)({0}, {1}, {2}) 数据个数:{3}", sumR, sumG, sumB, tmp);
+                    this.swLog.WriteLine(strr);
+                    strr = string.Format("均值:(R, G, B)({0}, {1}, {2})\r\n", (sumR * 1.0 / tmp).ToString("F2"), (sumG * 1.0 / tmp).ToString("F2"), (sumB * 1.0 / tmp).ToString("F2"));
+                    this.swLog.WriteLine(strr);
+                    this.swLog.WriteLine();
+                }
+
+                #endregion
+            }
+            else
+            {
+                #region 仅一图,无法去噪,直接计算总值
+                List<Color> img2 = this.lstImages[this.lstImages.Count - 1]; // 上一幅
+                for (int i = 0; i < img2.Count; i++)
+                {
+                    sumR += img2[i].R;
+                    sumG += img2[i].G;
+                    sumB += img2[i].B;
+                }
+                if (this.IsLog)
+                {
+                    int tmp = img2.Count;
+                    string strr = string.Format("总值:(R, G, B)({0}, {1}, {2}) 数据个数:{3}", sumR, sumG, sumB, tmp);
+                    this.swLog.WriteLine(strr);
+                    strr = string.Format("均值:(R, G, B)({0}, {1}, {2})\r\n", (sumR * 1.0 / tmp).ToString("F2"), (sumG * 1.0 / tmp).ToString("F2"), (sumB * 1.0 / tmp).ToString("F2"));
+                    this.swLog.WriteLine(strr);
+                    this.swLog.WriteLine();
+                }
+                #endregion
+            }
+        }
+        protected void calcAvg(ref int sumR, ref int sumG, ref int sumB)
+        {
+            if (this.lstImages == null)
+            {
+                throw new Exception("队列没有数据");
+            }
+
+            if (this.lstImages.Count >= 2)
+            {
+                // 计算滤噪后的总值
+                List<Color> img1 = this.lstImages[this.lstImages.Count - 2]; // 上一幅
+                List<Color> img2 = this.lstImages[this.lstImages.Count - 1]; // 当前幅
+
+                #region 计算后图减前图的差值
+                string strr = "R:\r\n", strg = "G:\r\n", strb = "B:\r\n";
+                List<IntRGB> imgsub = new List<IntRGB>();
+                IntRGB val;
+                for (int i = 0; i < img2.Count; i++)
+                {
+                    val = new IntRGB(
+                        img2[i].R - img1[i].R,
+                        img2[i].G - img1[i].G,
+                        img2[i].B - img1[i].B);
+                    imgsub.Add(val);
+
+                    if (this.IsLog)
+                    {
+                        strr = string.Format("{0}{1},", strr, val.R.ToString().PadLeft(3, ' '));
+                        strg = string.Format("{0}{1},", strg, val.G.ToString().PadLeft(3, ' '));
+                        strb = string.Format("{0}{1},", strb, val.B.ToString().PadLeft(3, ' '));
+                        if (i % this.LogWidth == this.LogWidth - 1)
+                        {
+                            strr = string.Format("{0}\r\n", strr);
+                            strg = string.Format("{0}\r\n", strg);
+                            strb = string.Format("{0}\r\n", strb);
+                        }
+                    }
+                }
+                if (this.IsLog)
+                {
+                    //strr = string.Format("{0}\r\n", strr);
+                    //strg = string.Format("{0}\r\n", strg);
+                    //strb = string.Format("{0}\r\n", strb);
+
+                    this.swLog.WriteLine("相减数据:");
+                    this.swLog.WriteLine(strr);
+                    this.swLog.WriteLine(strg);
+                    this.swLog.WriteLine(strb);
+                    this.swLog.WriteLine();
+                }
+                #endregion
+
+                #region 统计差值,以G值为准
+                Dictionary<int, int> dicimg = new Dictionary<int, int>();
+                foreach (IntRGB subval in imgsub)
+                {
+                    if (dicimg.Keys.Contains(subval.G))
+                    {
+                        dicimg[subval.G] += 1;
+                    }
+                    else
+                    {
+                        dicimg.Add(subval.G, 1);
+                    }
+                }
+                List<KeyValuePair<int, int>> lstsub = dicimg.OrderByDescending(c => c.Key).ToList();
+                #endregion
+
+                #region 计算阈值参数
+                NoiseParam npm = new NoiseParam(this.NoiseCnt);
+                npm.Threhold = img2.Count / 10;
+                int tmp = 0; // 计算上阈值
+                for (int i = 0; i < lstsub.Count; i++)
+                {
+                    tmp += lstsub[i].Value;
+                    if (tmp < npm.Threhold)
+                    {
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点+:" + lstsub[i].Key.ToString() + "[" + lstsub[i].Value.ToString() + "点]"); }
+                        continue; // 未够数,继续下一组
+                    }
+                    else if (tmp == npm.Threhold)
+                    {
+                        // 正好够数,记录阈值,退出循环
+                        npm.Upper = lstsub[i].Key;
+                        npm.UpCnt = lstsub[i].Value; // 该阈值需消除的点数
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点+:" + lstsub[i].Key.ToString() + "[" + npm.UpCnt.ToString() + "点]"); }
+                        break;
+                    }
+                    else
+                    {
+                        // 过数,计算阈值,退出循环
+                        npm.Upper = lstsub[i].Key;
+                        npm.UpCnt = lstsub[i].Value - (tmp - npm.Threhold);
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点+:" + lstsub[i].Key.ToString() + "[" + npm.UpCnt.ToString() + "点]"); }
+                        break;
+                    }
+                }
+                tmp = 0; // 计算下阈值
+                for (int i = lstsub.Count - 1; i >= 0; i--)
+                {
+                    tmp += lstsub[i].Value;
+                    if (tmp < npm.Threhold)
+                    {
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点-:" + lstsub[i].Key.ToString() + "[" + lstsub[i].Value.ToString() + "点]"); }
+                        continue; // 未够数,继续下一组
+                    }
+                    else if (tmp == npm.Threhold)
+                    {
+                        // 正好够数,记录阈值,退出循环
+                        npm.Lower = lstsub[i].Key;
+                        npm.LowCnt = lstsub[i].Value; // 该阈值需消除的点数
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点-:" + lstsub[i].Key.ToString() + "[" + npm.LowCnt.ToString() + "点]"); }
+                        break;
+                    }
+                    else
+                    {
+                        // 过数,计算阈值,退出循环
+                        npm.Lower = lstsub[i].Key;
+                        npm.LowCnt = lstsub[i].Value - (tmp - npm.Threhold);
+                        if (this.IsLog) { this.swLog.WriteLine("滤除噪点-:" + lstsub[i].Key.ToString() + "[" + npm.LowCnt.ToString() + "点]"); }
+                        break;
+                    }
+                }
+                #endregion
+
+                #region 滤除噪点后,计算总值
+                double tmpR = 0.0, tmpG = 0.0, tmpB = 0.0;
+                double tmpu = (dicimg[npm.Upper] - npm.UpCnt) * 1.0 / dicimg[npm.Upper]; // 部分过滤点保留的比率
+                if (this.IsLog)
+                {
+                    this.swLog.WriteLine(
+      string.Format("噪点+:{0}[{1}/{2}]={3}", npm.Upper.ToString(), npm.UpCnt, dicimg[npm.Upper], tmpu.ToString("F2")));
+                }
+                double tmpd = (dicimg[npm.Lower] - npm.LowCnt) * 1.0 / dicimg[npm.Lower]; // 部分过滤点保留的比率
+                if (this.IsLog)
+                {
+                    this.swLog.WriteLine(
+      string.Format("噪点-:{0}[{1}/{2}]={3}", npm.Lower.ToString(), npm.LowCnt, dicimg[npm.Lower], tmpd.ToString("F2")));
+                }
+                for (int i = 0; i < img2.Count; i++)
+                {
+                    if (imgsub[i].G > npm.Upper || imgsub[i].G < npm.Lower)
+                    {
+                        continue; // 噪点,滤去
+                    }
+                    else if (imgsub[i].G == npm.Upper && npm.UpCnt > 0)
+                    {
+                        // 部分噪点,滤去部分
+                        tmpR += img2[i].R * tmpu;
+                        tmpG += img2[i].G * tmpu;
+                        tmpB += img2[i].B * tmpu;
+                        continue;
+                    }
+                    else if (imgsub[i].G == npm.Lower && npm.LowCnt > 0)
+                    {
+                        // 部分噪点,滤去部分
+                        tmpR += img2[i].R * tmpd;
+                        tmpG += img2[i].G * tmpd;
+                        tmpB += img2[i].B * tmpd;
+                        continue;
+                    }
+                    else
+                    {
+                        // 非噪点,计算总值
+                        tmpR += img2[i].R;
+                        tmpG += img2[i].G;
+                        tmpB += img2[i].B;
+                    }
+                }
+                sumR = Convert.ToInt32(tmpR);
+                sumG = Convert.ToInt32(tmpG);
+                sumB = Convert.ToInt32(tmpB);
+                if (this.IsLog)
+                {
+                    tmp = img2.Count - npm.Threhold * 2;
+                    strr = string.Format("处理总值:(R, G, B)({0}, {1}, {2}), 数据个数:{3}, ", sumR, sumG, sumB, tmp)
+                        + string.Format("均值:(R, G, B)({0}, {1}, {2})", (sumR * 1.0 / tmp).ToString("F2"), (sumG * 1.0 / tmp).ToString("F2"), (sumB * 1.0 / tmp).ToString("F2"));
+                    this.swLog.WriteLine(strr);
+                    //this.swLog.WriteLine();
+                }
+
+                #endregion
+            }
+            else
+            {
+                #region 仅一图,无法去噪,直接计算总值
+                List<Color> img2 = this.lstImages[this.lstImages.Count - 1]; // 上一幅
+                for (int i = 0; i < img2.Count; i++)
+                {
+                    sumR += img2[i].R;
+                    sumG += img2[i].G;
+                    sumB += img2[i].B;
+                }
+                if (this.IsLog)
+                {
+                    int tmp = img2.Count;
+                    string strr = string.Format("处理总值:(R, G, B)({0}, {1}, {2}), 数据个数:{3}, ", sumR, sumG, sumB, tmp)
+                        + string.Format("均值:(R, G, B)({0}, {1}, {2})", (sumR * 1.0 / tmp).ToString("F2"), (sumG * 1.0 / tmp).ToString("F2"), (sumB * 1.0 / tmp).ToString("F2"));
+                    this.swLog.WriteLine(strr);
+                    //this.swLog.WriteLine();
+                }
+                #endregion
+            }
+        }
+
+        #endregion
+    }
+
+
+    /// <summary>
+    /// 用int类型保存RGB三个值
+    /// </summary>
+    public class IntRGB
+    {
+        #region properties
+        public int R { get; set; }
+        public int G { get; set; }
+        public int B { get; set; }
+        #endregion
+
+        public IntRGB()
+        {
+            this.R = 0;
+            this.G = 0;
+            this.B = 0;
+        }
+
+        public IntRGB(int r, int g, int b)
+        {
+            this.R = r;
+            this.G = g;
+            this.B = b;
+        }
+    }
+
+    /// <summary>
+    /// 噪点相关参数
+    /// </summary>
+    public class NoiseParam
+    {
+        /// <summary>
+        /// 前后两图相减法,去除上下噪点
+        /// </summary>
+        /// <param name="threhold">上下各自噪点数</param>
+        public NoiseParam(int threhold)
+        {
+            this.Threhold = threhold;
+            this.Upper = 0;
+            this.UpCnt = 0;
+            this.Lower = 0;
+            this.LowCnt = 0;
+        }
+
+
+        #region
+
+        /// <summary>
+        /// 噪点个数
+        /// </summary>
+        public int Threhold { get; set; }
+
+        /// <summary>
+        /// 噪点上阈值
+        /// </summary>
+        public int Upper { get; set; }
+        /// <summary>
+        /// 上阈值噪点数个数
+        /// </summary>
+        public int UpCnt { get; set; }
+
+        /// <summary>
+        /// 噪点下阈值
+        /// </summary>
+        public int Lower { get; set; }
+        /// <summary>
+        /// 下阈值噪点个数
+        /// </summary>
+        public int LowCnt { get; set; }
+
+        #endregion
+
+        #region
+
+        #endregion
+    }
+}

+ 60 - 0
SHJX.Service.Common/Camera/SensorsParameter.cs

@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Drawing;
+
+namespace SHJX.Service.Common.Camera
+{
+    public class SensorsParameter
+    {
+        #region properties
+        /// <summary>
+        /// 配置节点名称
+        /// </summary>
+        public string SectionName { get; set; }
+
+        /// <summary>
+        /// 摄像头采样区域
+        /// </summary>
+        public Rectangle RoiSample;
+
+        /// <summary>
+        /// 选用的摄像头名称,如:“Logitech HD Webcam C270”
+        /// </summary>
+        public string CamSensorName { get; set; }
+
+        /// <summary>
+        /// 是否自动定位像素处理目标区域位置
+        /// </summary>
+        public bool UseAutoUpdateRoiPos { get; set; }
+
+        /// <summary>
+        /// 是否使用目标区域精确像素定位:仅计算区域内G值大于指定阈值的像素点
+        /// </summary>
+        public bool UseRoiMask { get; set; }
+
+        /// <summary>
+        /// 目标区域内有效点的坐标
+        /// </summary>
+        public List<Point> RoiMaskPoints { get; set; }
+
+        /// <summary>
+        /// roi区域定位后,再进行偏移
+        /// </summary>
+        public int RoiOffsetX { get; set; }
+
+        #endregion
+
+        #region methods
+        public SensorsParameter()
+        {
+            SectionName = "Sensors";
+            RoiSample = new Rectangle(310, 210, 20, 20);
+            CamSensorName = "Logitech HD Webcam C270";//    "Logi C270 HD WebCam";//
+            UseAutoUpdateRoiPos = true;
+            UseRoiMask = true;
+            RoiMaskPoints = null;
+            RoiOffsetX = 0;
+        }
+
+        #endregion
+    }
+}

+ 10 - 0
SHJX.Service.Common/Camera/TitrationSettings.cs

@@ -0,0 +1,10 @@
+namespace SHJX.Service.Common.Camera
+{
+    public static class TitrationSettings
+    {
+        /// <summary>
+        /// 光线传感器参数
+        /// </summary>
+        public static SensorsParameter SensorPara;
+    }
+}

+ 397 - 0
SHJX.Service.Common/Camera/WebCamera.cs

@@ -0,0 +1,397 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Windows.Forms;
+using AForge.Video;
+using AForge.Video.DirectShow;
+using AForge.Controls;
+using System.Threading;
+
+namespace SHJX.Service.Common.Camera
+{
+    public class WebCamera
+    {
+        #region privates fields
+        public VideoCaptureDevice vidCapDevice { get; set; }
+        private VideoSourcePlayer vidPlayer { get; set; }
+        private Bitmap _currFrame;
+        #endregion
+
+
+        #region static
+        static FilterInfoCollection vidCollection = new FilterInfoCollection(FilterCategory.VideoInputDevice);
+        public static int GetCameraCounts()
+        {
+            return vidCollection.Count;
+        }
+
+        public static string GetCameraMoniker(int index)
+        {
+            return vidCollection[index].MonikerString;
+        }
+
+        public static string GetCameraName(int index)
+        {
+            return vidCollection[index].Name;
+        }
+        #endregion
+
+
+        #region events properties
+        /// <summary>
+        /// 画面刷新事件
+        /// </summary>
+        public event NewFrameEventHandler NewFrameEvent;
+
+        /// <summary>
+        /// 当前画面,相机开启时有效
+        /// </summary>
+        public Bitmap CurrFrame
+        {
+            get => vidCapDevice is not null ? _currFrame.Clone() as Bitmap : null;
+            protected set
+            {
+                _currFrame = value;
+            }
+        }
+        #endregion
+
+
+        #region public methods
+        /// <summary>
+        /// 摄像头是否已打开
+        /// </summary>
+        public bool IsOpened
+        {
+            get
+            {
+                bool isOpened = false;
+                if (vidPlayer == null)
+                {
+                    return isOpened;
+                }
+                if (vidPlayer.InvokeRequired)
+                {
+                    vidPlayer.Invoke(new Action(() =>
+                    {
+                        isOpened = vidPlayer.IsRunning;
+                    }));
+                }
+                else
+                {
+                    isOpened = vidPlayer.IsRunning;
+                }
+                return isOpened;
+            }
+        }
+
+        /// <summary>
+        /// 初始化摄像头
+        /// </summary>
+        /// <param name="cameraMoniker">摄像头名称</param>
+        public WebCamera(string cameraMoniker)
+        {
+            vidCapDevice = new VideoCaptureDevice(cameraMoniker);
+            vidCapDevice.NewFrame += GetCurrFrame;
+            vidPlayer = new VideoSourcePlayer
+            {
+                VideoSource = vidCapDevice
+            };
+            _currFrame = null;
+        }
+
+        public WebCamera()
+        {
+            vidCapDevice = new VideoCaptureDevice();
+            vidPlayer = new VideoSourcePlayer
+            {
+                VideoSource = vidCapDevice
+            };
+            _currFrame = null;
+        }
+
+        /// <summary>
+        /// 连接指定的摄像头,并打开视频信号
+        /// </summary>
+        /// <param name="cameraMoniker"></param>
+        /// <returns></returns>
+        public bool OpenCamera(string cameraMoniker)
+        {
+            this.vidCapDevice.Source = cameraMoniker;
+            vidCapDevice.NewFrame += this.GetCurrFrame;
+            this.vidCapDevice.Start();
+            return this.vidCapDevice.IsRunning;
+        }
+
+        /// <summary>
+        /// 设置显示摄像头图像区域
+        /// </summary>
+        /// <param name="parentControl"></param>
+        /// <param name="left"></param>
+        /// <param name="top"></param>
+        /// <param name="width"></param>
+        /// <param name="height"></param>
+        public void ShowControl(ref Panel parentControl, int left, int top, int width, int height)
+        {
+            if (vidPlayer == null) return;
+            parentControl.Controls.Add(vidPlayer);
+            vidPlayer.Location = new Point(left, top);
+            vidPlayer.Size = new Size(width, height);
+            vidPlayer.Visible = true;
+        }
+
+        /// <summary>
+        /// 显示摄像头属性设置窗体
+        /// </summary>
+        /// <param name="parentWindow"></param>
+        public void ShowProperties(IntPtr parentWindow)
+        {
+            vidCapDevice.DisplayPropertyPage(parentWindow);
+        }
+
+        /// <summary>
+        /// 设置相机属性值
+        /// </summary>
+        /// <param name="prop"></param>
+        /// <param name="val"></param>
+        /// <param name="flags"></param>
+        public void SetProperties(CameraControlProperty prop, int val, CameraControlFlags flags)
+        {
+            vidCapDevice.SetCameraProperty(prop, val, flags);// 取消自动曝光
+        }
+
+        /// <summary>
+        /// 读取当前属性值
+        /// </summary>
+        /// <param name="prop"></param>
+        /// <param name="val"></param>
+        /// <param name="flags"></param>
+        public void GetProperties(CameraControlProperty prop, out int val, out CameraControlFlags flags)
+        {
+            vidCapDevice.GetCameraProperty(prop, out val, out flags);
+        }
+
+        /// <summary>
+        /// 读取相机参数的取值范围
+        /// </summary>
+        /// <param name="prop"></param>
+        /// <param name="minValue"></param>
+        /// <param name="maxValue"></param>
+        /// <param name="stepSize"></param>
+        /// <param name="defaultValue"></param>
+        /// <param name="flags"></param>
+        public void GetPropertiesRange(CameraControlProperty prop, out int minValue, out int maxValue, out int stepSize, out int defaultValue, out CameraControlFlags flags)
+        {
+            vidCapDevice.GetCameraPropertyRange(prop, out minValue, out maxValue, out stepSize, out defaultValue, out flags);
+        }
+
+        /// <summary>
+        /// 设置视频属性值,亮度、对比度、白平衡、增益等
+        /// </summary>
+        /// <param name="prop"></param>
+        /// <param name="val"></param>
+        /// <param name="flags"></param>
+        public void SetVideoProcAmpProperties(VideoProcAmpProperty prop, int val, VideoProcAmpFlags flags)
+        {
+            vidCapDevice.SetVideoProcAmpProperty(prop, val, flags);
+        }
+
+        /// <summary>
+        /// 读取当前视频参数
+        /// </summary>
+        /// <param name="prop"></param>
+        /// <param name="val"></param>
+        /// <param name="flags"></param>
+        public void GetVideoProcAmpProperties(VideoProcAmpProperty prop, out int val, out VideoProcAmpFlags flags)
+        {
+            vidCapDevice.GetVideoProcAmpProperty(prop, out val, out flags);
+        }
+
+        /// <summary>
+        /// 读取视频参数的取值范围
+        /// </summary>
+        /// <param name="prop"></param>
+        /// <param name="minValue"></param>
+        /// <param name="maxValue"></param>
+        /// <param name="stepSize"></param>
+        /// <param name="defaultValue"></param>
+        /// <param name="flags"></param>
+        public void GetVideoProcAmpPropertiesRange(VideoProcAmpProperty prop, out int minValue, out int maxValue, out int stepSize, out int defaultValue, out VideoProcAmpFlags flags)
+        {
+            vidCapDevice.GetVideoProcAmpPropertyRange(prop, out minValue, out maxValue, out stepSize, out defaultValue, out flags);
+        }
+
+        /// <summary>
+        /// 返回视频支持的分辨率
+        /// </summary>
+        /// <returns></returns>
+        public List<string> GetVideoResolution()
+        {
+            List<string> resolution = new List<string>();
+            foreach (var cap in vidCapDevice.VideoCapabilities)
+            {
+                resolution.Add(string.Format("{0} x {1}", cap.FrameSize.Width, cap.FrameSize.Height));
+            }
+            return resolution;
+        }
+
+        /// <summary>
+        /// 设定视频的分辨率,index可以通过GetVideoResolution()获取所有支持的分辨率来确定
+        /// </summary>
+        /// <param name="index">在所有支持分辨率中的序号</param>
+        public void SetVideoResolution(int index)
+        {
+            if (vidCapDevice.VideoCapabilities.Length > index)
+            {
+                vidCapDevice.VideoResolution = vidCapDevice.VideoCapabilities[index];
+            }
+        }
+
+        /// <summary>
+        /// 打开摄像头
+        /// </summary>
+        /// <returns></returns>
+        public bool OpenCamera()
+        {
+            if (vidCapDevice != null && vidCapDevice.IsRunning == false)
+            {
+                vidCapDevice.NewFrame += NewFrameEvent;
+                vidCapDevice.Start();
+            }
+
+            if (vidPlayer.InvokeRequired)
+            {
+                vidPlayer.Invoke(new Action(() =>
+                {
+                    if (vidPlayer != null && !vidPlayer.IsRunning)
+                    {
+                        vidPlayer.Start();
+                    }
+                }));
+            }
+            else
+            {
+                if (vidPlayer != null && !vidPlayer.IsRunning)
+                {
+                    vidPlayer.Start();
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 关闭摄像头
+        /// </summary>
+        public void CloseCamera()
+        {
+            if (vidCapDevice != null && vidCapDevice.IsRunning)
+            {
+                vidCapDevice.Stop();
+                vidCapDevice.NewFrame -= NewFrameEvent;
+            }
+
+            if (vidPlayer.InvokeRequired)
+            {
+                vidPlayer.Invoke(new Action(() =>
+                {
+                    if (vidPlayer == null || !vidPlayer.IsRunning) return;
+                    vidPlayer.SignalToStop();
+                    vidPlayer.WaitForStop();
+                }));
+            }
+            else
+            {
+                if (vidPlayer == null || !vidPlayer.IsRunning) return;
+                vidPlayer.SignalToStop();
+                vidPlayer.WaitForStop();
+            }
+
+        }
+
+        /// <summary>
+        /// 拍照
+        /// </summary>
+        /// <returns></returns>
+        public Bitmap GrabImage()
+        {
+            Bitmap bmp = null;
+
+            if (vidPlayer.InvokeRequired)
+            {
+                vidPlayer.Invoke(new Action(() =>
+                {
+                    if (vidPlayer.IsRunning)
+                    {
+                        bmp = vidPlayer.GetCurrentVideoFrame();
+                    }
+                    else
+                    {
+                        // 探头未连接,重试
+                        int cnt = 0; // 重试10秒
+                        while (!vidPlayer.IsRunning && cnt < 100)
+                        {
+                            vidPlayer.Start();
+                            Thread.Sleep(100);
+                            cnt++;
+                            //PublicDatas.LogProgram.Error("摄像头未连接,正在重试" + cnt.ToString());
+                        }
+                        Thread.Sleep(300);
+                        bmp = vidPlayer.GetCurrentVideoFrame();
+                    }
+                }));
+            }
+            else
+            {
+                if (vidPlayer.IsRunning)
+                {
+                    bmp = vidPlayer.GetCurrentVideoFrame();
+                }
+                else
+                {
+                    // 探头未连接,重试
+                    int cnt = 0; // 重试10秒
+                    while (!vidPlayer.IsRunning && cnt < 100)
+                    {
+                        vidPlayer.Start();
+                        Thread.Sleep(100);
+                        cnt++;
+                        // PublicDatas.LogProgram.Error("摄像头未连接,正在重试" + cnt.ToString());
+                    }
+                    Thread.Sleep(300);
+                    bmp = vidPlayer.GetCurrentVideoFrame();
+                }
+            }
+            return bmp;
+        }
+        /// <summary>
+        /// 拍照,返回CurrFrame图像
+        /// </summary>
+        /// <returns></returns>
+        public Bitmap GrabImage2()
+        {
+            return CurrFrame;
+        }
+
+        /// <summary>
+        /// 实时将新的画面存入CurrFrame对象中
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        public void GetCurrFrame(object sender, NewFrameEventArgs e)
+        {
+            try
+            {
+                if (e.Frame != null)
+                {
+                    CurrFrame = e.Frame;
+                }
+            }
+            catch (Exception)
+            {
+                // ignored
+            }
+        }
+
+        #endregion
+    }
+}

+ 72 - 0
SHJX.Service.Common/CustomUtil/DataCache.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Runtime.Caching;
+
+namespace SHJX.Service.Common.CustomUtil
+{
+    /// <summary>
+    /// 数据缓存工具类
+    /// </summary>
+    public static class DataCache
+    {
+        private static readonly MemoryCache cache = new("ErrorFromEmsCache");
+        /// <summary>
+        /// 在缓存中新增数据
+        /// </summary>
+        /// <param name="detailInfo"></param>
+        public static void Set<T>(string key, T value)
+        {
+            var policy = new CacheItemPolicy
+            {
+                AbsoluteExpiration = DateTime.Now.AddHours(15)
+            };
+            if (!Contains(key))
+            {
+                cache.Set(key, value, policy);
+            }
+            cache[key] = value;
+        }
+
+        /// <summary>
+        /// 查询缓存中是否包含指定键
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public static bool Contains(string key)
+        {
+            return cache.Contains(key);
+        }
+
+        /// <summary>
+        /// 获取缓存中数据
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public static T Get<T>(string key)
+        {
+            try
+            {
+                if (Contains(key))
+                {
+                    return (T)cache[key];
+                }
+                return default;
+            }
+            catch (Exception)
+            {
+                return default;
+            }
+        }
+
+        /// <summary>
+        /// 移除缓存中的数据
+        /// </summary>
+        /// <param name="key"></param>
+        public static void Remove(string key)
+        {
+            if (Contains(key))
+            {
+                cache.Remove(key);
+            }
+        }
+    }
+}

+ 67 - 0
SHJX.Service.Common/Extend/CancelCache.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Caching;
+
+namespace SHJX.Service.Common.Extend
+{
+    public class CancelCache
+    {
+        private static readonly MemoryCache cache = new("CancelCache");
+        /// <summary>
+        /// 在缓存中新增数据
+        /// </summary>
+        /// <param name="errinfo"></param>
+        public static void Set(string key, object value)
+        {
+            var policy = new CacheItemPolicy
+            {
+                AbsoluteExpiration = DateTime.Now.AddMinutes(15)
+            };
+            if (!Contains(key))
+            {
+                cache.Set(key, value, policy);
+            }
+            cache[key] = value;
+        }
+        /// <summary>
+        /// 查询缓存中是否包含指定键
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public static bool Contains(string key)
+        {
+            return cache.Contains(key);
+        }
+        /// <summary>
+        /// 获取缓存中数据
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public static string Get(string key)
+        {
+            try
+            {
+                if (Contains(key))
+                {
+                    return cache[key].ToString();
+                }
+                return null;
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+        /// <summary>
+        /// 移除缓存中的数据
+        /// </summary>
+        /// <param name="key"></param>
+        public static void Remove(string key)
+        {
+            if (Contains(key))
+            {
+                MemoryCache.Default.Remove(key);
+            }
+        }
+    }
+}

+ 788 - 0
SHJX.Service.Common/Extend/Converter.cs

@@ -0,0 +1,788 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Data;
+using NPOI.SS.Util;
+using System.Drawing;
+using System.Windows;
+using System.Reflection;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+using NPOI.HSSF.UserModel;
+using System.Globalization;
+using System.ComponentModel;
+using SHJX.Service.Model.Dao;
+using System.Drawing.Imaging;
+using System.Collections.Generic;
+using System.Windows.Media.Imaging;
+using Microsoft.Office.Interop.Word;
+using SHJX.Service.Common.Calculate;
+using DataTable = System.Data.DataTable;
+using Application = Microsoft.Office.Interop.Word.Application;
+using HorizontalAlignment = NPOI.SS.UserModel.HorizontalAlignment;
+
+using OfficeOpenXml;
+using OfficeOpenXml.Style;
+using LicenseContext = OfficeOpenXml.LicenseContext;
+
+namespace SHJX.Service.Common.Extend
+{
+    public static class Converter
+    {
+        #region ExcelConvert
+
+        /// <summary>
+        /// DataTable -> Excel
+        /// </summary>
+        /// <param name="dt">DataTable数据</param>
+        /// <param name="filePath">文件路径</param>
+        /// <param name="headLineName">头部行 默认无</param>
+        public static void ConvertToExcel(this DataTable dt, string filePath, string headLineName = null)
+        {
+            var fileExt = Path.GetExtension(filePath).ToLower();
+            IWorkbook workbook = fileExt switch
+            {
+                ".xlsx" => new XSSFWorkbook(),
+                ".xls" => new HSSFWorkbook(),
+                _ => null
+            };
+            if (workbook is null) return;
+            var sheet = string.IsNullOrEmpty(dt.TableName) ? workbook.CreateSheet("Sheet1") : workbook.CreateSheet(dt.TableName);
+            var rowNumber = 0;
+            if (headLineName is not null)
+            {
+                var rowHead = sheet?.CreateRow(rowNumber);
+                rowNumber++;
+                sheet?.AddMergedRegion(new CellRangeAddress(0, 0, 0, dt.Columns.Count - 1));
+                var cellHead = rowHead?.CreateCell(0);
+                //设置单元格内容
+                cellHead?.SetCellValue(headLineName);
+                var style = workbook.CreateCellStyle();
+                //设置单元格的样式:水平对齐居中
+                style.Alignment = HorizontalAlignment.Center;
+                //新建一个字体样式对象
+                var font = workbook.CreateFont();
+                font.FontHeightInPoints = 20;
+                //设置字体加粗样式
+                //使用SetFont方法将字体样式添加到单元格样式中 
+                style.SetFont(font);
+                //将新的样式赋给单元格
+                if (cellHead is not null) cellHead.CellStyle = style;
+            }
+
+            //表头
+            if (sheet is not null)
+            {
+                var row = sheet.CreateRow(rowNumber);
+                for (var i = 0; i < dt.Columns.Count; i++)
+                {
+                    var cell = row.CreateCell(i);
+                    cell.SetCellValue(dt.Columns[i].ColumnName);
+                    if (!rowNumber.Equals(0)) continue;
+                    var style = workbook.CreateCellStyle();
+                    //设置单元格的样式:水平对齐居中
+                    style.Alignment = HorizontalAlignment.Center;
+                    //新建一个字体样式对象
+                    var font = workbook.CreateFont();
+                    font.FontHeightInPoints = 12;
+                    font.Boldweight = (short)FontBoldWeight.Bold;
+                    style.SetFont(font);
+                    cell.CellStyle = style;
+                }
+            }
+
+            //数据绑定
+            for (var i = 0; i < dt.Rows.Count; i++)
+            {
+                if (sheet is null) continue;
+                var row1 = sheet.CreateRow(i + rowNumber + 1);
+                for (var j = 0; j < dt.Columns.Count; j++)
+                {
+                    var cell = row1.CreateCell(j);
+                    if (dt.Rows[i][j].GetType().IsValueType)
+                    {
+                        cell.SetCellValue(Convert.ToDouble(dt.Rows[i][j]));
+                    }
+                    else
+                    {
+                        cell.SetCellValue(dt.Rows[i][j].ToString());
+                    }
+                }
+            }
+
+            //转为字节数组  
+            var stream = new MemoryStream();
+            workbook.Write(stream);
+            var buf = stream.ToArray();
+            //保存为Excel文件  
+            using var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
+            fs.Write(buf, 0, buf.Length);
+            fs.Flush();
+        }
+
+        /// <summary>
+        /// Excel->DataTable
+        /// </summary>
+        /// <param name="filePath">Excel文件路径</param>
+        public static DataTable ReadExcel(this string filePath)
+        {
+            IWorkbook iwkX;
+            using (var fs = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+            {
+                iwkX = WorkbookFactory.Create(fs);
+                fs.Close();
+            }
+            //sheet
+            var dt = new DataTable();
+            for (var h = 0; h < iwkX.NumberOfSheets; h++)
+            {
+                var sheet = iwkX.GetSheetAt(h);
+                var rows = sheet.GetRowEnumerator();
+                var isMove = rows.MoveNext();
+                //循环sheet
+                if (!isMove) continue;
+                var cols = (IRow)rows.Current;
+                dt.TableName = sheet.SheetName;
+                for (var i = 0; i < cols?.LastCellNum; i++)
+                {
+                    dt.Columns.Add(cols.GetCell(i).ToString());
+                }
+                while (rows.MoveNext())
+                {
+                    var row = (IRow)rows.Current;
+                    var dr = dt.NewRow();
+                    for (var i = 0; i < row?.LastCellNum; i++)
+                    {
+                        var cell = row.GetCell(i);
+                        dr[i] = cell is null ? string.Empty : cell.ToString();
+                    }
+                    dt.Rows.Add(dr);
+                }
+            }
+
+            return dt;
+        }
+
+        #endregion
+
+        #region DataTable -> List<T>
+
+        /// <summary>
+        /// 将DataTable转换为List
+        /// </summary>
+        /// <param name="dt"></param>
+        /// <returns></returns>
+        public static List<T> ConvertToList<T>(this DataTable dt) where T : class, new()
+        {
+            List<T> lists = new List<T>(); // 定义集合
+            foreach (DataRow dr in dt.Rows) //遍历DataTable中所有的数据行
+            {
+                T t = new();
+                PropertyInfo[] propertys = typeof(T).GetProperties(); // 获得此模型的公共属性
+                foreach (PropertyInfo item in propertys) //遍历该对象的所有属性
+                {
+                    object[] attr = item.GetCustomAttributes(typeof(DescriptionAttribute), false);
+                    string description = attr.Length.Equals(0) ? item.Name : ((DescriptionAttribute)attr[0]).Description;
+                    //检查DataTable是否包含此列(列名等于对象的属性的Description属性)
+                    if (!dt.Columns.Contains(description)) continue;
+                    if (!item.CanWrite) continue; //该属性不可写,直接跳出
+                    object value = dr[description];//取值
+                    if (value.Equals(DBNull.Value)) continue;//如果非空,则赋给对象的属性
+                    value = item.PropertyType.FullName switch
+                    {
+                        "System.String" => value.ToString(),
+                        "System.Double" => Convert.ToDouble(value),
+                        "System.Boolean" => value.Equals("1"),
+                        "System.Int32" => Convert.ToInt32(value),
+                        _ => throw new ArgumentNullException(item.PropertyType.FullName),
+                    };
+                    item.SetValue(t, value, null);
+                }
+                lists.Add(t); //对象添加到泛型集合中
+            }
+            return lists;
+        }
+
+        #endregion
+
+        #region List<EquipmentTask> -> Word
+        public static bool ConvertToExcel(this List<EquipmentTask> tasks, object path)
+        {
+            DataTable dt = new("Result");
+            dt.Columns.Add("类型", Type.GetType("System.String")!);
+            dt.Columns.Add("样品编号", Type.GetType("System.String")!);
+            dt.Columns.Add("取样体积", Type.GetType("System.String")!);
+            dt.Columns.Add("取样倍数", Type.GetType("System.String")!);
+            dt.Columns.Add("浓度", Type.GetType("System.String")!);
+            dt.Columns.Add("滴定体积", Type.GetType("System.String")!);
+            dt.Columns.Add("结果", Type.GetType("System.String")!);
+            dt.Columns.Add("分析时间", Type.GetType("System.String")!);
+            List<EquipmentTask> calibrationTasks = tasks.Where(item => item.TaskType.Equals("标定")).ToList();
+            //if (calibrationTasks is null or { Count: 0 })
+            //{
+            //    calibrationTasks = DataCentre._dataManager.GetLastOtherTaskByWaveKey(item, level);
+            //    tempCount = 0; GetLastOtherTaskByWaveKey("空白", task.SampleConcentration);// SearchOtherTask(TaskTypeName.CALIBRATION_ZH);
+            //}
+            if (calibrationTasks is not null)
+            {
+                InsertDataTable(calibrationTasks);
+            }
+            List<EquipmentTask> blankTasks = tasks.Where(item => item.TaskType.Equals("空白")).ToList();  
+            //if (blankTasks is null or { Count: 0 })
+            //{
+            //    blankTasks = GeBlankTaskResult(TaskTypeName.BLANK_ZH);
+            //}
+            if (blankTasks is not null)
+            {
+                InsertDataTable(blankTasks);
+            }
+            List<EquipmentTask> sampleTasks = tasks.Where(item => item.TaskType.Equals("水样")).ToList();
+            if (sampleTasks is not null)
+            {
+                InsertDataTable(sampleTasks);
+            }
+            dt.ExportResult(path.ToString(), tasks[0].WaveKey);
+
+            void InsertDataTable(List<EquipmentTask> insertTasks)
+            {
+                insertTasks.ForEach(task =>
+                {
+                    dt.Rows.Add(task.TaskType, task.TaskDetailName, task.GetSampleVolume.ToString("F2"),
+                        task.GetSampleMultiple.ToString("F2"), task.SampleConcentration.Equals("Low")?"低":"高",task.Amount.ToString("F2"),
+                        task.Result.ToString("F2"), task.CreateTime.ToString("yyyy-MM-dd"));
+                });
+            }
+            return true;
+        }
+        public static void ExportResult(this DataTable dt, string filePath,string wavekay)
+        {
+            string savePath = $"{filePath}\\SampleResult_{wavekay}.xlsx";
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+            using ExcelPackage package = new();
+            ExcelWorksheet sheet = package.Workbook.Worksheets.Add("SampleResult");
+            for (int i = 0; i < dt.Columns.Count; i++)
+            {
+                sheet.Column(i + 1).Width = 15;
+            }
+            sheet.Row(1).Style.Font.Bold = true;
+            sheet.Row(1).Style.Font.Size = 12;
+            sheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+            sheet.Row(1).Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+            //表头设置
+            for (int i = 0; i < dt.Columns.Count; i++)
+            {
+                sheet.SetValue(1, i + 1, dt.Columns[i].ColumnName);
+            }
+            //数据绑定
+            for (int i = 0; i < dt.Rows.Count; i++)
+            {
+                sheet.Row(i + 2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+                sheet.Row(i + 2).Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+                for (int j = 0; j < dt.Columns.Count; j++)
+                {
+                    sheet.SetValue(i + 2, j + 1, dt.Rows[i][j].GetType().IsValueType ? Convert.ToDouble(dt.Rows[i][j]) : dt.Rows[i][j].ToString());
+                }
+            }
+            byte[] bytes = package.GetAsByteArray();
+            File.WriteAllBytes(savePath, bytes);
+        }
+        public static bool ConvertToWord(this List<EquipmentTask> tasks, object path, double maxVloume)
+        {
+            var tableColumn = 11;
+            try
+            {
+              
+                Document wordDoc; //Word文档变量
+                Application wordApp; //Word应用程序变量
+                wordApp = new ApplicationClass
+                {
+                    Visible = false //使文档可见
+                };
+                //由于使用的是COM库,因此有许多变量需要用Missing.Value代替
+                object nothing = Missing.Value;
+                wordDoc = wordApp.Documents.Add(ref nothing, ref nothing, ref nothing, ref nothing);
+
+                #region 页面样式设置
+                wordDoc.PageSetup.PaperSize = WdPaperSize.wdPaperA4; //设置纸张样式为A4纸
+                wordDoc.PageSetup.Orientation = WdOrientation.wdOrientPortrait; //排列方式为垂直方向
+                wordDoc.PageSetup.TopMargin = 57.0f;
+                wordDoc.PageSetup.BottomMargin = 57.0f;
+                wordDoc.PageSetup.LeftMargin = 57.0f;
+                wordDoc.PageSetup.RightMargin = 57.0f;
+                wordDoc.PageSetup.HeaderDistance = 30.0f; //页眉位置
+                PageNumbers pns = wordApp.Selection.Sections[1].Headers[WdHeaderFooterIndex.wdHeaderFooterEvenPages].PageNumbers; //获取当前页的号码
+                pns.NumberStyle = WdPageNumberStyle.wdPageNumberStyleNumberInDash; //设置页码的风格,是Dash形还是圆形的
+                pns.HeadingLevelForChapter = 0;
+                pns.IncludeChapterNumber = false;
+                pns.RestartNumberingAtSection = false;
+                pns.StartingNumber = 0; //开始页页码?
+                object pagenmbetal = WdPageNumberAlignment.wdAlignPageNumberCenter; //将号码设置在中间
+                object first = true;
+                wordApp.Selection.Sections[1].Footers[WdHeaderFooterIndex.wdHeaderFooterEvenPages].PageNumbers
+                    .Add(ref pagenmbetal, ref first);
+                object unite = WdUnits.wdStory;
+                #endregion
+
+                #region 添加表格、填充数据、设置表格行列宽高、合并单元格、添加表头斜线、给单元格添加图片
+
+                //wordDoc.Content.InsertAfter("\n"); //插入换行
+
+                wordApp.Selection.EndKey(ref unite, ref nothing); //将光标移动到文档末尾
+                wordApp.Selection.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphLeft;
+                var kbLowTasks = tasks.Where(item => item.TaskType.Equals("空白") && item.SampleConcentration.Equals("Low")).ToList();
+                var kbHighTasks = tasks.Where(item => item.TaskType.Equals("空白") && item.SampleConcentration.Equals("High")).ToList();
+
+                var syTasks = tasks.Where(item => item.TaskType.Equals("水样")).ToList();
+               // var syLowTasks = tasks.Where(item => item.TaskType.Equals("水样") && item.SampleConcentration.Equals("Low")).ToList();
+               // var syHighTasks = tasks.Where(item => item.TaskType.Equals("水样") && item.SampleConcentration.Equals("High")).ToList();
+
+                var bdLowTasks = tasks.Where(item => item.TaskType.Equals("标定") && item.SampleConcentration.Equals("Low")).ToList();
+                var bdHighTasks = tasks.Where(item => item.TaskType.Equals("标定") && item.SampleConcentration.Equals("High")).ToList();
+
+                SampleCalculate.tempCount = 0;
+                kbLowTasks.GeBlankTaskResult(maxVloume, out var noneValueL);
+                SampleCalculate.tempCount = 0;
+                kbHighTasks.GeBlankTaskResult(maxVloume, out var noneValueH);
+                bdLowTasks.GetBDTaskResult(maxVloume, "Low", out var bdValueL);
+                bdHighTasks.GetBDTaskResult(maxVloume, "High", out var bdValueH);
+
+                int tableRow = 5 + 5 + 3 + 3 + 2; //设置表格的行数
+
+                tableRow += kbLowTasks.Count; //低浓度空白样
+                tableRow += kbHighTasks.Count;
+                tableRow += syTasks.Count;
+                //tableRow += syLowTasks.Count; //
+                //tableRow += syHighTasks.Count; //水样
+                tableRow += bdLowTasks.Count; //低浓度标定样
+                tableRow += bdHighTasks.Count;
+
+                //定义一个Word中的表格对象
+                var table = wordDoc.Tables.Add(wordApp.Selection.Range, tableRow, tableColumn, ref nothing,
+                    ref nothing);
+                //默认创建的表格没有边框,这里修改其属性,使得创建的表格带有边框(这个值可以设置得很大,例如5、13等等)
+                table.Borders.Enable = 1;
+
+                #region 头部
+
+                table.Cell(1, 1).Range.Text = "化学需氧量原始记录表";
+                table.Cell(1, 1).Range.Font.Size = 12F;
+                table.Cell(1, 1).Range.Bold = 1;
+                table.Rows[1].Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter; //表格文本居中
+                table.Rows[1].Range.Cells.VerticalAlignment =
+                    WdCellVerticalAlignment.wdCellAlignVerticalCenter; //文本垂直居中
+
+                table.Cell(2, 1).Range.Text = "方法依据";
+                table.Cell(2, 2).Range.Text = "《水质化学需氧量的测定重铬酸钾法》HJ828-2017";
+                table.Cell(2, 8).Range.Text = "检出限";
+                table.Cell(2, 10).Range.Text = "4mg/l";
+                table.Cell(3, 1).Range.Text = "重铬酸钾配置日期";
+                table.Cell(3, 5).Range.Text = "重铬酸钾浓度";
+                table.Cell(3, 8).Range.Text = "分析日期";
+                table.Cell(4, 1).Range.Text = "样品编号或名称";
+                table.Cell(4, 2).Range.Text = "氯离子浓度(mg/l)";
+                table.Cell(4, 3).Range.Text = "硫酸汞加入量(ml)";
+                table.Cell(4, 4).Range.Text = "取样体积(ml)";
+                table.Cell(4, 5).Range.Text = "稀释倍数";
+                table.Cell(4, 6).Range.Text = "稀释定容体积(ml)";
+                table.Cell(4, 7).Range.Text = "硫酸亚铁铵标准溶液消耗量(ml)";
+                table.Cell(4, 10).Range.Text = "浓度高低";
+                table.Cell(4, 11).Range.Text = "计算结果(mg/l)或者ml";
+                table.Cell(5, 7).Range.Text = "V 始";
+                table.Cell(5, 8).Range.Text = "V 终";
+                table.Cell(5, 9).Range.Text = "V终-V始";
+
+                table.Cell(1, 1).Merge(table.Cell(1, 11));
+                table.Rows[1].Height = 35; //设置新增加的这行表格的高度
+                table.Cell(2, 10).Merge(table.Cell(2, 11));
+                table.Cell(2, 8).Merge(table.Cell(2, 9));
+                table.Cell(2, 2).Merge(table.Cell(2, 7));
+
+                table.Cell(3, 10).Merge(table.Cell(3, 11));
+                table.Cell(3, 8).Merge(table.Cell(3, 9));
+                table.Cell(3, 5).Merge(table.Cell(3, 6));
+                table.Cell(3, 3).Merge(table.Cell(3, 4));
+                table.Cell(3, 1).Merge(table.Cell(3, 2));
+
+                for (int i = 1; i < 7; i++)
+                    table.Cell(4, i).Merge(table.Cell(5, i));
+                table.Cell(4, 11).Merge(table.Cell(5, 11));
+                table.Cell(4, 10).Merge(table.Cell(5, 10));
+                table.Cell(4, 7).Merge(table.Cell(4, 9));
+
+                #endregion
+
+                var startRow = 6;
+                var endRows = 5;
+                var startRow2 = 0;
+
+
+                #region 空白样
+
+                #region 低浓度
+                if (kbLowTasks.Any())
+                {
+                    foreach (var item in kbLowTasks)
+                    {
+                        endRows++;
+                        table.Cell(endRows, 1).Range.Text = item.TaskDetailName;
+                        table.Cell(endRows, 3).Range.Text = item.HgSO4Volume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 4).Range.Text = item.GetSampleVolume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 5).Range.Text = item.GetSampleMultiple.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 6).Range.Text = "10";
+                        table.Cell(endRows, 7).Range.Text = "0";
+                        table.Cell(endRows, 8).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 9).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 10).Range.Text = "低";
+                    }
+                    SampleCalculate.tempCount = 0;
+                    //kbLowTasks.GeBlankTaskResult(maxVloume, out var noneValue);
+                    table.Cell(startRow, 11).Range.Text = noneValueL.ToString(CultureInfo.CurrentCulture);
+                    table.Cell(startRow, 11).Merge(table.Cell(endRows, 11)); //合并
+                }
+                #endregion
+
+                #region 高浓度
+                startRow = endRows + 1;
+                if (kbHighTasks.Any())
+                {
+                    foreach (var item in kbHighTasks)
+                    {   endRows++;
+                        table.Cell(endRows, 1).Range.Text = item.TaskDetailName;
+                        table.Cell(endRows, 3).Range.Text = item.HgSO4Volume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 4).Range.Text = item.GetSampleVolume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 5).Range.Text = item.GetSampleMultiple.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 6).Range.Text = "10";
+                        table.Cell(endRows, 7).Range.Text = "0";
+                        table.Cell(endRows, 8).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 9).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 10).Range.Text = "高";
+                    }
+                    SampleCalculate.tempCount = 0;
+                    //kbHighTasks.GeBlankTaskResult(maxVloume, out var noneValue);
+                    table.Cell(startRow, 11).Range.Text = noneValueH.ToString(CultureInfo.CurrentCulture);
+                    table.Cell(startRow, 11).Merge(table.Cell(endRows, 11)); //合并
+                }
+                #endregion
+
+                #endregion
+
+                #region 水样
+
+                if (syTasks.Any())
+                {
+                    syTasks.ForEach(item =>
+                    {
+                        endRows++;
+                        table.Cell(endRows, 1).Range.Text = item.TaskDetailName;
+                        table.Cell(endRows, 3).Range.Text = item.HgSO4Volume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 4).Range.Text = item.GetSampleVolume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 5).Range.Text = item.GetSampleMultiple.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 6).Range.Text = "10";
+                        table.Cell(endRows, 7).Range.Text = "0";
+                        table.Cell(endRows, 8).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 9).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 10).Range.Text = item.SampleConcentration == "Low" ? "低" : "高";
+                        table.Cell(endRows, 11).Range.Text = item.Result.ToString(CultureInfo.CurrentCulture);
+                        //table.Cell(endRows, 10).Merge(table.Cell(endRows, 11)); //合并
+                    });
+                }
+
+                #endregion
+
+                //9-13
+                #region 标定样
+                startRow2 = endRows+1;
+                #region 低浓度
+                if (bdLowTasks.Any())
+                {
+                    foreach (var item in bdLowTasks)
+                    {
+                        endRows++;
+                        table.Cell(endRows, 1).Range.Text = item.TaskDetailName;
+                        table.Cell(endRows, 3).Range.Text = item.HgSO4Volume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 4).Range.Text = item.GetSampleVolume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 5).Range.Text = item.GetSampleMultiple.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 6).Range.Text = "10";
+                        table.Cell(endRows, 7).Range.Text = "0";
+                        table.Cell(endRows, 8).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 9).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 10).Range.Text = "低";
+                    }
+                    //bdLowTasks.GetBDTaskResult(maxVloume, "Low",out var bdValue);
+                    table.Cell(startRow2, 11).Range.Text = bdValueL.ToString(CultureInfo.CurrentCulture);
+                    table.Cell(startRow2, 11).Merge(table.Cell(endRows, 11)); //合并
+                }
+                #endregion
+
+                #region 高浓度
+                startRow2 = endRows + 1;
+                if (bdHighTasks.Any())
+                {
+                    foreach (var item in bdHighTasks)
+                    {
+                        endRows++;
+                        table.Cell(endRows, 1).Range.Text = item.TaskDetailName;
+                        table.Cell(endRows, 3).Range.Text = item.HgSO4Volume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 4).Range.Text = item.GetSampleVolume.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 5).Range.Text = item.GetSampleMultiple.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 6).Range.Text = "10";
+                        table.Cell(endRows, 7).Range.Text = "0";
+                        table.Cell(endRows, 8).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 9).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture);
+                        table.Cell(endRows, 10).Range.Text = "高";
+                    }
+                    //bdHighTasks.GetBDTaskResult(maxVloume, "High",out var bdValue2);
+                    table.Cell(startRow2, 11).Range.Text = bdValueH.ToString(CultureInfo.CurrentCulture);
+                    table.Cell(startRow2, 11).Merge(table.Cell(endRows, 11)); //合并
+                }
+                #endregion
+
+                #endregion
+                #region 标定样
+
+                //if (bdLowTasks.Any())
+                //{
+                //    table.Cell(endRows + 1, 1).Range.Text = "硫酸亚铁铵标准溶液的标定";
+                //    table.Cell(endRows + 1, 3).Range.Text = "硫酸亚铁铵消耗体积(ml)";
+                //    table.Cell(endRows + 1, 6).Range.Text = "硫酸亚铁铵浓度(mol/l)";
+                //    table.Cell(endRows + 1, 7).Range.Text = "化学需氧量计算公式";
+
+                //    table.Cell(endRows + 2, 3).Range.Text = "V始";
+                //    table.Cell(endRows + 2, 4).Range.Text = "V终";
+                //    table.Cell(endRows + 2, 5).Range.Text = "V终-V始";
+
+                //    int bdRows = 2;
+                //    foreach (var item in bdLowTasks)
+                //    {
+                //        bdRows++;
+                //        table.Cell(endRows + bdRows, 3).Range.Text = "0";
+                //        table.Cell(endRows + bdRows, 4).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture); //V终
+                //        table.Cell(endRows + bdRows, 5).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture); //V终-V始
+                //    }
+
+                //    table.Cell(endRows + 1, 8).Merge(table.Cell(endRows + 5, 11));
+                //    table.Cell(endRows + 1, 7).Merge(table.Cell(endRows + 5, 7));
+                //    table.Cell(endRows + 1, 6).Merge(table.Cell(endRows + 2, 6)); //硫酸亚铁铵浓度(mol/l)
+                //    table.Cell(endRows + 3, 6).Merge(table.Cell(endRows + 5, 6));
+                //    table.Cell(endRows + 1, 3).Merge(table.Cell(endRows + 1, 5)); //硫酸亚铁铵消耗体积(ml)
+                //    table.Cell(endRows + 1, 1).Merge(table.Cell(endRows + 5, 2));
+                //}
+
+                //if (bdHighTasks.Any())
+                //{
+                //    table.Cell(endRows + 1, 1).Range.Text = "硫酸亚铁铵标准溶液的标定";
+                //    table.Cell(endRows + 1, 3).Range.Text = "硫酸亚铁铵消耗体积(ml)";
+                //    table.Cell(endRows + 1, 6).Range.Text = "硫酸亚铁铵浓度(mol/l)";
+                //    table.Cell(endRows + 1, 7).Range.Text = "化学需氧量计算公式";
+
+                //    table.Cell(endRows + 2, 3).Range.Text = "V始";
+                //    table.Cell(endRows + 2, 4).Range.Text = "V终";
+                //    table.Cell(endRows + 2, 5).Range.Text = "V终-V始";
+                //    int bdRows = 2;
+                //    foreach (var item in bdHighTasks)
+                //    {
+                //        bdRows++;
+                //        table.Cell(endRows + bdRows, 3).Range.Text = "0";
+                //        table.Cell(endRows + bdRows, 4).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture); //V终
+                //        table.Cell(endRows + bdRows, 5).Range.Text = item.Amount.ToString(CultureInfo.CurrentCulture); //V终-V始
+                //    }
+                //    table.Cell(endRows + 1, 8).Merge(table.Cell(endRows + 5, 11));
+                //    table.Cell(endRows + 1, 7).Merge(table.Cell(endRows + 5, 7));
+                //    table.Cell(endRows + 1, 6).Merge(table.Cell(endRows + 2, 6)); //硫酸亚铁铵浓度(mol/l)
+                //    table.Cell(endRows + 3, 6).Merge(table.Cell(endRows + 5, 6));
+                //    table.Cell(endRows + 1, 3).Merge(table.Cell(endRows + 1, 5)); //硫酸亚铁铵消耗体积(ml)
+                //    table.Cell(endRows + 1, 1).Merge(table.Cell(endRows + 5, 2));
+                //}
+                #endregion
+
+                endRows += 5;
+                //14-16
+
+                #region 准确度
+
+                table.Cell(endRows + 1, 1).Range.Text = "准确度";
+                table.Cell(endRows + 1, 2).Range.Text = "质控样品编号";
+                table.Cell(endRows + 1, 4).Range.Text = "保证值(mg/l)";
+                table.Cell(endRows + 1, 6).Range.Text = "测试含量(mg/l)";
+                table.Cell(endRows + 1, 8).Range.Text = "质控样类型";
+                table.Cell(endRows + 1, 10).Range.Text = "是否合格";
+
+                table.Cell(endRows + 2, 8).Range.Text = "\u00A3密码";
+                table.Cell(endRows + 2, 9).Range.Text = "\u00A3明码";
+                table.Cell(endRows + 2, 10).Range.Text = "\u00A3合格";
+                table.Cell(endRows + 2, 11).Range.Text = "\u00A3不合格";
+                table.Cell(endRows + 3, 8).Range.Text = "\u00A3密码";
+                table.Cell(endRows + 3, 9).Range.Text = "\u00A3明码" ;
+                table.Cell(endRows + 3, 10).Range.Text = "\u00A3合格";
+                table.Cell(endRows + 3, 11).Range.Text = "\u00A3不合格";
+
+                for (int i = endRows + 2; i < endRows + 4; i++)
+                    for (int j = 8; j < 12; j++)
+                        table.Cell(i, j).Range.Font.Name = "Wingdings 2";
+
+                table.Cell(endRows + 2, 6).Merge(table.Cell(endRows + 2, 7));
+                table.Cell(endRows + 2, 4).Merge(table.Cell(endRows + 2, 5));
+                table.Cell(endRows + 2, 2).Merge(table.Cell(endRows + 2, 3));
+
+                table.Cell(endRows + 3, 6).Merge(table.Cell(endRows + 3, 7));
+                table.Cell(endRows + 3, 4).Merge(table.Cell(endRows + 3, 5));
+                table.Cell(endRows + 3, 2).Merge(table.Cell(endRows + 3, 3));
+                table.Cell(endRows + 1, 1).Merge(table.Cell(endRows + 3, 1));
+
+                #endregion
+
+                endRows += 3;
+                //17-19
+
+                #region 精密度
+
+                table.Cell(endRows + 1, 1).Range.Text = "精密度";
+                table.Cell(endRows + 1, 2).Range.Text = "平行样品编号";
+                table.Cell(endRows + 1, 7).Range.Text = "平行样编号";
+
+                table.Cell(endRows + 2, 2).Range.Text = "测试含量(mg/l)";
+                table.Cell(endRows + 2, 7).Range.Text = "平行样编号";
+
+                table.Cell(endRows + 3, 2).Range.Text = "相对偏差(%)";
+                table.Cell(endRows + 3, 7).Range.Text = "平行样编号";
+
+                table.Cell(endRows + 1, 9).Merge(table.Cell(endRows + 1, 11));
+                table.Cell(endRows + 1, 7).Merge(table.Cell(endRows + 1, 8));
+                table.Cell(endRows + 1, 4).Merge(table.Cell(endRows + 1, 6));
+                table.Cell(endRows + 1, 2).Merge(table.Cell(endRows + 1, 3));
+
+                table.Cell(endRows + 2, 9).Merge(table.Cell(endRows + 2, 10));
+                table.Cell(endRows + 2, 7).Merge(table.Cell(endRows + 2, 8));
+                table.Cell(endRows + 2, 4).Merge(table.Cell(endRows + 2, 5));
+                table.Cell(endRows + 2, 2).Merge(table.Cell(endRows + 2, 3));
+
+                table.Cell(endRows + 3, 9).Merge(table.Cell(endRows + 3, 11));
+                table.Cell(endRows + 3, 7).Merge(table.Cell(endRows + 3, 8));
+                table.Cell(endRows + 3, 4).Merge(table.Cell(endRows + 3, 6));
+                table.Cell(endRows + 3, 2).Merge(table.Cell(endRows + 3, 3));
+
+                table.Cell(endRows + 1, 1).Merge(table.Cell(endRows + 3, 1));
+
+                #endregion
+
+                endRows += 3;
+                //20-21
+
+                #region 结尾
+
+                table.Cell(endRows + 1, 1).Range.Text = "分析人:";
+                table.Cell(endRows + 1, 4).Range.Text = "校准人:";
+                table.Cell(endRows + 1, 8).Range.Text = "审核人:";
+                table.Cell(endRows + 1, 8).Merge(table.Cell(endRows + 1, 11));
+                table.Cell(endRows + 1, 4).Merge(table.Cell(endRows + 1, 7));
+                table.Cell(endRows + 1, 1).Merge(table.Cell(endRows + 1, 3));
+
+
+                table.Cell(endRows + 2, 1).Range.Text = "日期:     年   月   日";
+                table.Cell(endRows + 2, 4).Range.Text = "日期:       年     月     日";
+                table.Cell(endRows + 2, 8).Range.Text = "日期:       年     月     日";
+                table.Cell(endRows + 2, 8).Merge(table.Cell(endRows + 2, 11));
+                table.Cell(endRows + 2, 4).Merge(table.Cell(endRows + 2, 7));
+                table.Cell(endRows + 2, 1).Merge(table.Cell(endRows + 2, 3));
+
+                #endregion
+
+                #endregion
+
+                wordApp.Selection.EndKey(ref unite, ref nothing); //将光标移动到文档末尾
+                wordDoc.Content.InsertAfter("\n");
+                //WdSaveFormat为Word 2003文档的保存格式
+                object format = WdSaveFormat.wdFormatDocument;
+                //将wordDoc文档对象的内容保存为DOCX文档
+                string cat = tasks[0].WaveKey;
+                string cat2 = path.ToString();
+                cat2 = cat2 + "\\SampleResult_" + cat + ".doc";
+                path = (object)cat2;
+                wordDoc.SaveAs(ref path, ref format, ref nothing, ref nothing, ref nothing, ref nothing, ref nothing,
+                    ref nothing, ref nothing, ref nothing, ref nothing, ref nothing, ref nothing, ref nothing,
+                    ref nothing,
+                    ref nothing);
+                wordDoc.Close(ref nothing, ref nothing, ref nothing); //关闭wordApp组件对象
+                wordApp.Quit(ref nothing, ref nothing, ref nothing); //退出wordApp组件对象
+                return true;
+            }
+            catch (Exception)
+            {
+                throw new Exception();
+            }
+        }
+
+        #endregion
+
+        #region 截图转换成Bitmap
+        /// <summary>
+        /// 截图转换成bitmap
+        /// </summary>
+        /// <param name="element"></param>
+        /// <param name="width">默认控件宽度</param>
+        /// <param name="height">默认控件高度</param>
+        /// <param name="x">默认0</param>
+        /// <param name="y">默认0</param>
+        /// <returns></returns>
+        public static Bitmap ScreenConvertToBitmap(this System.Windows.Media.Visual visual)
+        {
+            int width = 0, height = 0, x = 0, y = 0;
+            Rect rc = SystemParameters.WorkArea; //获取工作区大小
+            if (width == 0) width = (int)rc.Width;
+            if (height == 0) height = (int)rc.Height;
+            var rtb = new RenderTargetBitmap(width, height, x, y, System.Windows.Media.PixelFormats.Default);
+            rtb.Render(visual);
+            Bitmap bit = BitmapSourceToBitmap(rtb);
+            return bit;
+        }
+
+        /// <summary>
+        /// BitmapSource转Bitmap
+        /// </summary>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        public static Bitmap BitmapSourceToBitmap(BitmapSource source)
+        {
+            return BitmapSourceToBitmap(source, source.PixelWidth, source.PixelHeight);
+        }
+
+        /// <summary>
+        /// Convert BitmapSource to Bitmap
+        /// </summary>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        public static Bitmap BitmapSourceToBitmap(BitmapSource source, int width, int height)
+        {
+            Bitmap bmp = null;
+            try
+            {
+                PixelFormat format = PixelFormat.Format24bppRgb;
+                /*set the translate type according to the in param(source)*/
+                switch (source.Format.ToString())
+                {
+                    case "Rgb24":
+                    case "Bgr24": format = PixelFormat.Format24bppRgb; break;
+                    case "Bgra32": format = PixelFormat.Format32bppPArgb; break;
+                    case "Bgr32": format = PixelFormat.Format32bppRgb; break;
+                    case "Pbgra32": format = PixelFormat.Format32bppArgb; break;
+                }
+                bmp = new Bitmap(width, height, format);
+                BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(System.Drawing.Point.Empty, bmp.Size),
+                    ImageLockMode.WriteOnly,
+                    format);
+                source.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
+                bmp.UnlockBits(data);
+            }
+            catch
+            {
+                if (bmp != null)
+                {
+                    bmp.Dispose();
+                    bmp = null;
+                }
+            }
+            return bmp;
+        }
+        #endregion
+    }
+}

+ 117 - 0
SHJX.Service.Common/Extend/Encryption.cs

@@ -0,0 +1,117 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Web.Security;
+using System.Security.Cryptography;
+
+namespace SHJX.Service.Common.Extend
+{
+    /// <summary>
+    /// 加密
+    /// </summary>
+    public static class Encryption
+    {
+        /// <summary>
+        /// MD5字符串加密
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns>加密后字符串</returns>
+        public static string GenerateMd5(this string str)
+        {
+            using var md = MD5.Create();
+            var buffer = Encoding.Default.GetBytes(str);
+            //开始加密
+            var newBuffer = md.ComputeHash(buffer);
+            var sb = new StringBuilder();
+            foreach (var item in newBuffer)
+            {
+                sb.Append(item.ToString("x2"));
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// MD5流加密
+        /// </summary>
+        /// <param name="inputStream"></param>
+        /// <returns></returns>
+        public static string GenerateMd5(this Stream inputStream)
+        {
+            using var md = MD5.Create();
+            //开始加密
+            var newBuffer = md.ComputeHash(inputStream);
+            var sb = new StringBuilder();
+            foreach (var item in newBuffer)
+            {
+                sb.Append(item.ToString("x2"));
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// DES数据加密
+        /// </summary>
+        /// <param name="targetValue">目标值</param>
+        /// <param name="key">密钥</param>
+        /// <returns>加密值</returns>
+        public static string Encrypt(this string targetValue, string key)
+        {
+            if (string.IsNullOrEmpty(targetValue)) return string.Empty;
+            var returnValue = new StringBuilder();
+            var des = new DESCryptoServiceProvider();
+            var inputByteArray = Encoding.Default.GetBytes(targetValue);
+            // 通过两次哈希密码设置对称算法的初始化向量  
+            des.Key = Encoding.ASCII.GetBytes(FormsAuthentication.HashPasswordForStoringInConfigFile
+                (FormsAuthentication.HashPasswordForStoringInConfigFile(key, "md5")?.Substring(0, 8)!, "sha1")?.Substring(0, 8)!);
+            // 通过两次哈希密码设置算法的机密密钥  
+            des.IV = Encoding.ASCII.GetBytes(FormsAuthentication.HashPasswordForStoringInConfigFile
+                (FormsAuthentication.HashPasswordForStoringInConfigFile(key, "md5")?.Substring(0, 8)!, "md5")?.Substring(0, 8)!);
+            var ms = new MemoryStream();
+            var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
+            cs.Write(inputByteArray, 0, inputByteArray.Length);
+            cs.FlushFinalBlock();
+            foreach (byte b in ms.ToArray())
+            {
+                returnValue.AppendFormat("{0:X2}", b);
+            }
+            return returnValue.ToString();
+        }
+
+
+        /// <summary>
+        /// DES数据解密
+        /// </summary>
+        /// <param name="targetValue"></param>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public static string Decrypt(this string targetValue, string key)
+        {
+            if (string.IsNullOrEmpty(targetValue)) return string.Empty;
+            // 定义DES加密对象
+            var des = new DESCryptoServiceProvider();
+            int len = targetValue.Length / 2;
+            var inputByteArray = new byte[len];
+            int x, i;
+            for (x = 0; x < len; x++)
+            {
+                i = Convert.ToInt32(targetValue.Substring(x * 2, 2), 16);
+                inputByteArray[x] = (byte)i;
+            }
+            // 通过两次哈希密码设置对称算法的初始化向量  
+            des.Key = Encoding.ASCII.GetBytes(FormsAuthentication.HashPasswordForStoringInConfigFile
+                (FormsAuthentication.HashPasswordForStoringInConfigFile(key, "md5")
+                    ?.Substring(0, 8)!, "sha1")?.Substring(0, 8)!);
+            // 通过两次哈希密码设置算法的机密密钥  
+            des.IV = Encoding.ASCII.GetBytes(FormsAuthentication.HashPasswordForStoringInConfigFile
+                (FormsAuthentication.HashPasswordForStoringInConfigFile(key, "md5")
+                    ?.Substring(0, 8)!, "md5")?.Substring(0, 8)!);
+            // 定义内存流
+            var ms = new MemoryStream();
+            // 定义加密流
+            var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
+            cs.Write(inputByteArray, 0, inputByteArray.Length);
+            cs.FlushFinalBlock();
+            return Encoding.Default.GetString(ms.ToArray());
+        }
+    }
+}

+ 474 - 0
SHJX.Service.Common/Extend/ExportWordByNPOI.cs

@@ -0,0 +1,474 @@
+using NPOI.XWPF.UserModel;
+using NPOI.OpenXmlFormats.Wordprocessing;
+using SHJX.Service.Model.Dao;
+using System;
+using System.IO;
+using System.Linq;
+using System.Data;
+using NPOI.SS.Util;
+using System.Collections.Generic;
+namespace SHJX.Service.Common.Extend
+{
+    public class ExportWordByNPOI : NpoiWordParagraphTextStyle
+    {
+        public override void Write(List<EquipmentTask> tasks, string savePath)
+        {
+            string fileName = string.Format("GMSY_Result_by_{0}.docx", DateTime.Now.ToString("yyyyMMddHHmmss"), System.Text.Encoding.UTF8);
+            if (!Directory.Exists(savePath))
+            {
+                Directory.CreateDirectory(savePath);
+            }
+            using var stream = new FileStream(Path.Combine(savePath, fileName), FileMode.Create, FileAccess.Write);
+
+            //创建document文档对象对象实例
+            XWPFDocument document = new XWPFDocument();
+            CT_SectPr m_SectPr = new CT_SectPr();   //实例⼀个尺⼨类的实例
+            m_SectPr.pgSz.w = 16838;                //设置宽度(这⾥是⼀个ulong类型)
+            m_SectPr.pgSz.h = 11906;                //设置⾼度(这⾥是⼀个ulong类型)
+            XWPFDocument doc = new XWPFDocument();
+            document.Document.body.sectPr = m_SectPr;    //设置页⾯的尺⼨
+
+            #region Table Header
+            //创建文档中的表格对象实例
+            XWPFTable headTable = document.CreateTable(4, 4);   //显示的行列数rows:4行,cols:4列
+            headTable.Width = 5200;//总宽度
+            headTable.SetColumnWidth(0, 1300); /* 设置列宽 */
+            headTable.SetColumnWidth(1, 1100); /* 设置列宽 */
+            headTable.SetColumnWidth(2, 1400); /* 设置列宽 */
+            headTable.SetColumnWidth(3, 1400); /* 设置列宽 */
+
+            //Table 表格第一行展示...后面的都是一样,只改变GetRow中的行数
+            headTable.GetRow(0).MergeCells(0, 3);//合并3列
+            headTable.GetRow(0).GetCell(0).SetParagraph(SetTableParagraph(document, headTable, "水质高锰酸盐指数", ParagraphAlignment.CENTER, 24, true));
+
+            //Table 表格第二行
+            headTable.GetRow(1).GetCell(0).SetParagraph(SetTableParagraph(document, headTable, "采样日期", ParagraphAlignment.CENTER, 24, true));
+            headTable.GetRow(1).GetCell(1).SetParagraph(SetTableParagraph(document, headTable, string.Empty, ParagraphAlignment.CENTER, 24, false));
+            headTable.GetRow(1).GetCell(2).SetParagraph(SetTableParagraph(document, headTable, "分析日期", ParagraphAlignment.CENTER, 24, true));
+            headTable.GetRow(1).GetCell(3).SetParagraph(SetTableParagraph(document, headTable, string.Empty, ParagraphAlignment.CENTER, 24, false));
+
+            //Table 表格第三行
+            headTable.GetRow(2).GetCell(0).SetParagraph(SetTableParagraph(document, headTable, "消解时间", ParagraphAlignment.CENTER, 24, true));
+            headTable.GetRow(2).GetCell(1).SetParagraph(SetTableParagraph(document, headTable, string.Empty, ParagraphAlignment.CENTER, 24, false));
+            headTable.GetRow(2).GetCell(2).SetParagraph(SetTableParagraph(document, headTable, "检查次数", ParagraphAlignment.CENTER, 24, true));
+            headTable.GetRow(2).GetCell(3).SetParagraph(SetTableParagraph(document, headTable, string.Empty, ParagraphAlignment.CENTER, 24, false));
+
+            headTable.GetRow(3).MergeCells(1, 3);//合并3列
+            headTable.GetRow(3).GetCell(0).SetParagraph(SetTableParagraph(document, headTable, "分析方法名称及依据", ParagraphAlignment.CENTER, 24, false));
+            headTable.GetRow(3).GetCell(1).SetParagraph(SetTableParagraph(document, headTable, "《水质高锰酸盐指数的测定(GB 11892-89)》、《国家地表水环境质量监测网监测任务作业指导书(2017)》", ParagraphAlignment.LEFT, 10, false));
+            #endregion
+
+            #region 标定
+            var acidTask = tasks.Where(item => item.TaskType.Equals(TaskTypeName.CALIBRATION_ZH) && item.AcidBaseProp.Equals(AcidBase.Acid)).ToList();
+            double acidAvg = 0;
+            var acid_k = CalculationResult.CalculateType(acidTask, (arg) => 10 / arg, ref acidAvg);
+            var alkaliTask = tasks.Where(item => item.TaskType.Equals(TaskTypeName.CALIBRATION_ZH) && item.AcidBaseProp.Equals(AcidBase.Alkali)).ToList();
+            double alkaliAvg = 0;
+            var alkali_k = CalculationResult.CalculateType(alkaliTask, (arg) => 10 / arg, ref alkaliAvg);
+
+            XWPFTable calibrationTable = document.CreateTable(2, 4);
+            calibrationTable.Width = 5200;//总宽度
+            calibrationTable.SetColumnWidth(0, 1300); /* 设置列宽 */
+            calibrationTable.SetColumnWidth(1, 1100); /* 设置列宽 */
+            calibrationTable.SetColumnWidth(2, 1400); /* 设置列宽 */
+            calibrationTable.SetColumnWidth(3, 1400); /* 设置列宽 */
+
+            calibrationTable.GetRow(0).MergeCells(0, 3);//合并3列
+            calibrationTable.GetRow(0).GetCell(0).SetParagraph(SetTableParagraph(document, calibrationTable, "标定", ParagraphAlignment.CENTER, 24, true));
+
+            calibrationTable.GetRow(1).MergeCells(1, 2);//合并3列
+            calibrationTable.GetRow(1).GetCell(0).SetParagraph(SetTableParagraph(document, calibrationTable, "草酸钠标液浓度", ParagraphAlignment.CENTER, 24, true));
+            calibrationTable.GetRow(1).GetCell(1).SetParagraph(SetTableParagraph(document, calibrationTable, "标定高锰酸钾体积V2(ml)", ParagraphAlignment.CENTER, 24, true));
+            calibrationTable.GetRow(1).GetCell(2).SetParagraph(SetTableParagraph(document, calibrationTable, "K=10/v2", ParagraphAlignment.CENTER, 24, true));
+
+            CT_Row nr = new CT_Row();
+            XWPFTableRow mr = new XWPFTableRow(nr, calibrationTable);//创建行 
+            calibrationTable.AddRow(mr);//将行添加到table中 
+            XWPFTableCell c1 = mr.CreateCell();//创建单元格
+            CT_Tc ct = c1.GetCTTc();
+            CT_TcPr cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.restart;//合并行
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = " 0.01mol/l";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "酸性法";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = $"{acidAvg:F2}";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = $"{acid_k:F2}";
+
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, calibrationTable);
+            calibrationTable.AddRow(mr);
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.@continue;//合并行 序号
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "碱性法";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = $"{alkaliAvg:F2}";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = $"{alkali_k:F2}";
+            #endregion
+
+            #region Sample Head
+            XWPFTable sampleHeadTable = document.CreateTable(1, 7);
+            sampleHeadTable.Width = 5180;//总宽度
+            for (int i = 0; i < 7; i++)
+            {
+                sampleHeadTable.SetColumnWidth(i, 740); /* 设置列宽 */
+            }
+
+            sampleHeadTable.GetRow(0).MergeCells(0, 6);//合并3列
+            sampleHeadTable.GetRow(0).GetCell(0).SetParagraph(SetTableParagraph(document, sampleHeadTable, "水样测试", ParagraphAlignment.CENTER, 24, true));
+
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, calibrationTable);//创建行 
+            sampleHeadTable.AddRow(mr);//将行添加到table中 
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.restart;//合并行
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "序号";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.restart;//合并行
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "样品编号";
+
+            string[] title = new string[] { "测定", "取样", "稀释", "高锰酸钾溶液" };
+
+            foreach (var item in title)
+            {
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVAlign().val = ST_VerticalJc.center;
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+                ct.GetPList()[0].AddNewR().AddNewT().Value = item;
+            }
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.restart;//合并行
+            cp.AddNewVAlign().val = ST_VerticalJc.center;
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "样品浓度(mg/L)";
+
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, sampleHeadTable);
+            sampleHeadTable.AddRow(mr);
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.@continue;//合并行 序号
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.@continue;//合并行 序号
+
+            title = new string[] { "方法", "体积(ml)", "倍数", "消耗量(ml)" };
+
+            foreach (var item in title)
+            {
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVAlign().val = ST_VerticalJc.center;
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;//单元格内容居中显示
+                ct.GetPList()[0].AddNewR().AddNewT().Value = item;
+            }
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.@continue;//合并行 序号
+            #endregion
+
+            #region Sample Content
+            List<EquipmentTask> blankTasks = tasks.Where(item => item.TaskType.Equals(TaskTypeName.BLANK_ZH)).ToList();
+            List<EquipmentTask> sampleTasks = tasks.Where(item => item.TaskType.Equals(TaskTypeName.SAMPLE_ZH)).ToList();
+            int sampleCount = blankTasks.Count + sampleTasks.Count;
+            XWPFTable sampleContentTable = document.CreateTable(sampleCount, 7);
+            sampleContentTable.Width = 5180;//总宽度
+            for (int i = 0; i < 7; i++)
+            {
+                sampleContentTable.SetColumnWidth(i, 740); /* 设置列宽 */
+            }
+            int index = 0;
+            foreach (var item in blankTasks)
+            {
+                sampleContentTable.GetRow(index).GetCell(0).SetParagraph(SetTableParagraph(document, headTable, $"{index}", ParagraphAlignment.CENTER, 24, true));
+                sampleContentTable.GetRow(index).GetCell(1).SetParagraph(SetTableParagraph(document, headTable, item.TaskType, ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(2).SetParagraph(SetTableParagraph(document, headTable, item.AcidBaseProp.Equals(AcidBase.Acid) ? "酸性法" : "碱性法", ParagraphAlignment.CENTER, 24, true));
+                sampleContentTable.GetRow(index).GetCell(3).SetParagraph(SetTableParagraph(document, headTable, $"{item.SampleVolume:F2}", ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(4).SetParagraph(SetTableParagraph(document, headTable, $"{item.SampleMultiple:F2}", ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(5).SetParagraph(SetTableParagraph(document, headTable, $"{item.Amount:F2}", ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(6).SetParagraph(SetTableParagraph(document, headTable, $"{item.Result:F2}", ParagraphAlignment.CENTER, 24, false));
+                index++;
+            }
+            foreach (var item in sampleTasks)
+            {
+                sampleContentTable.GetRow(index).GetCell(0).SetParagraph(SetTableParagraph(document, headTable, $"{index}", ParagraphAlignment.CENTER, 24, true));
+                sampleContentTable.GetRow(index).GetCell(1).SetParagraph(SetTableParagraph(document, headTable, item.TaskType, ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(2).SetParagraph(SetTableParagraph(document, headTable, item.AcidBaseProp.Equals(AcidBase.Acid) ? "酸性法" : "碱性法", ParagraphAlignment.CENTER, 24, true));
+                sampleContentTable.GetRow(index).GetCell(3).SetParagraph(SetTableParagraph(document, headTable, $"{item.SampleVolume:F2}", ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(4).SetParagraph(SetTableParagraph(document, headTable, $"{item.SampleMultiple:F2}", ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(5).SetParagraph(SetTableParagraph(document, headTable, $"{item.Amount:F2}", ParagraphAlignment.CENTER, 24, false));
+                sampleContentTable.GetRow(index).GetCell(6).SetParagraph(SetTableParagraph(document, headTable, $"{item.Result:F2}", ParagraphAlignment.CENTER, 24, false));
+                index++;
+            }
+            #endregion
+
+            #region Table Footer
+            XWPFTable footTable = document.CreateTable(1, 7);
+            footTable.Width = 5180;//总宽度
+            for (int i = 0; i < 7; i++)
+            {
+                footTable.SetColumnWidth(i, 740); /* 设置列宽 */
+            }
+            footTable.RemoveRow(0);
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, footTable);//创建行 
+            footTable.AddRow(mr);//将行添加到table中 
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.restart;//合并行
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "准确度";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.gridSpan = new CT_DecimalNumber();
+            cp.gridSpan.val = Convert.ToString(2); //合并列 
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "质控样品编号";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "真值(mg/l)";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.gridSpan = new CT_DecimalNumber();
+            cp.gridSpan.val = Convert.ToString(2); //合并列 
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "测试含量(mg/l)";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "是否合格";
+
+
+            for (int i = 0; i < 2; i++)
+            {
+                nr = new CT_Row();
+                mr = new XWPFTableRow(nr, footTable);//创建行 
+                footTable.AddRow(mr);//将行添加到table中 
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVMerge().val = ST_Merge.@continue;//合并行
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+
+
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.gridSpan = new CT_DecimalNumber();
+                cp.gridSpan.val = Convert.ToString(2); //合并列 
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+
+                for (int j = 0; j < 4; j++)
+                {
+                    c1 = mr.CreateCell();//创建单元格
+                    ct = c1.GetCTTc();
+                    cp = ct.AddNewTcPr();
+                    cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                    ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                }
+            }
+
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, footTable);//创建行 
+            footTable.AddRow(mr);//将行添加到table中 
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.restart;//合并行
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "精密度";
+
+            for (int i = 0; i < 2; i++)
+            {
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                ct.GetPList()[0].AddNewR().AddNewT().Value = "平行样编号";
+
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.gridSpan = new CT_DecimalNumber();
+                cp.gridSpan.val = Convert.ToString(2); //合并列 
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            }
+
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, footTable);//创建行 
+            footTable.AddRow(mr);//将行添加到table中 
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVMerge().val = ST_Merge.@continue;//合并行
+
+            for (int i = 0; i < 2; i++)
+            {
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                ct.GetPList()[0].AddNewR().AddNewT().Value = "测试含量(mg/l)";
+                for (int j = 0; j < 2; j++)
+                {
+                    c1 = mr.CreateCell();//创建单元格
+                    ct = c1.GetCTTc();
+                    cp = ct.AddNewTcPr();
+                    cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                    ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                }
+            }
+
+            title = new string[] { "均值", "相对偏差(%)" };
+
+            foreach (var item in title)
+            {
+                nr = new CT_Row();
+                mr = new XWPFTableRow(nr, footTable);//创建行 
+                footTable.AddRow(mr);//将行添加到table中 
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVMerge().val = ST_Merge.@continue;//合并行
+                for (int i = 0; i < 2; i++)
+                {
+                    c1 = mr.CreateCell();//创建单元格
+                    ct = c1.GetCTTc();
+                    cp = ct.AddNewTcPr();
+                    cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                    ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                    ct.GetPList()[0].AddNewR().AddNewT().Value = item;
+
+                    c1 = mr.CreateCell();//创建单元格
+                    ct = c1.GetCTTc();
+                    cp = ct.AddNewTcPr();
+                    cp.gridSpan = new CT_DecimalNumber();
+                    cp.gridSpan.val = Convert.ToString(2); //合并列 
+                    cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                    ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                }
+            }
+
+            nr = new CT_Row();
+            mr = new XWPFTableRow(nr, footTable);//创建行 
+            footTable.AddRow(mr);//将行添加到table中 
+
+            title = new string[] { "分析人", "校准人" };
+            foreach (var item in title)
+            {
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+                ct.GetPList()[0].AddNewR().AddNewT().Value = item;
+
+                c1 = mr.CreateCell();//创建单元格
+                ct = c1.GetCTTc();
+                cp = ct.AddNewTcPr();
+                cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+                ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            }
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+            ct.GetPList()[0].AddNewR().AddNewT().Value = "审核人";
+
+            c1 = mr.CreateCell();//创建单元格
+            ct = c1.GetCTTc();
+            cp = ct.AddNewTcPr();
+            cp.gridSpan = new CT_DecimalNumber();
+            cp.gridSpan.val = Convert.ToString(2); //合并列 
+            cp.AddNewVAlign().val = ST_VerticalJc.center;//垂直
+            ct.GetPList()[0].AddNewPPr().AddNewJc().val = ST_Jc.center;
+
+
+            #endregion
+
+            document.Write(stream);
+        }
+    }
+}

+ 83 - 0
SHJX.Service.Common/Extend/ExtendUtil.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Reflection;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Text.Encodings.Web;
+
+namespace SHJX.Service.Common.Extend
+{
+    /// <summary>
+    /// 扩展
+    /// </summary>
+    public static class ExtendUtil
+    {
+        #region 包含反转
+        /// <summary>
+        /// 包含反转
+        /// </summary>
+        /// <param name="value"></param>
+        /// <param name="values"></param>
+        /// <returns></returns>
+        public static bool In(this object value, params object[] values) =>
+            values.Contains(value);
+
+        public static bool In<T>(this T value, params T[] values) where T : class =>
+            values.Contains(value);
+        #endregion
+
+        /// <summary>
+        /// 根据值得到中文备注
+        /// </summary>
+        /// <param name="t"></param>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static string GetEnumDesc<T>(this Enum value) where T : Enum
+        {
+            Type t = typeof(T);
+            FieldInfo[] fields = t.GetFields();
+            for (int i = 1, count = fields.Length; i < count; i++)
+            {
+                if (Enum.Parse(t, fields[i].Name) != value) continue;
+                var enumAttributes = (DescriptionAttribute[])fields[i].GetCustomAttributes(typeof(DescriptionAttribute), false);
+                if (enumAttributes.Length > 0)
+                {
+                    return enumAttributes[0].Description;
+                }
+            }
+            return string.Empty;
+        }
+
+        public static string Uuid => Guid.NewGuid().ToString("N").ToUpper();
+        
+        public static Collection<T> AddRange<T>(this Collection<T> collection, IEnumerable<T> items)
+        {
+            if (collection == null)
+                throw new ArgumentNullException(nameof (collection));
+            if (items == null)
+                throw new ArgumentNullException(nameof (items));
+            foreach (T obj in items)
+                collection.Add(obj);
+            return collection;
+        }
+        
+        #region 深克隆
+        /// <summary>
+        /// Clones the specified list.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="List">The list.</param>
+        /// <returns>List{``0}.</returns>
+        public static List<T> Clone<T>(this List<T> list) where T : class
+        {
+            var deserializeSettings = JsonSerializer.Serialize(list, new JsonSerializerOptions
+            {
+                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+            });
+            return JsonSerializer.Deserialize<List<T>>(deserializeSettings);
+        }
+        #endregion
+    }
+}

+ 92 - 0
SHJX.Service.Common/Extend/NpoiWordParagraphTextStyle.cs

@@ -0,0 +1,92 @@
+using NPOI.XWPF.UserModel;
+using NPOI.OpenXmlFormats.Wordprocessing;
+using SHJX.Service.Model.Dao;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Data;
+using NPOI.SS.Util;
+using System.Drawing;
+using System.Windows;
+
+namespace SHJX.Service.Common.Extend
+{
+    public abstract class NpoiWordParagraphTextStyle
+    {
+        /// <summary>
+        /// 创建word文档中的段落对象和设置段落文本的基本样式(字体大小,字体,字体颜色,字体对齐位置)
+        /// </summary>
+        /// <param name="document">document文档对象</param>
+        /// <param name="fillContent">段落第一个文本对象填充的内容</param>
+        /// <param name="isBold">是否加粗</param>
+        /// <param name="fontSize">字体大小</param>
+        /// <param name="fontFamily">字体</param>
+        /// <param name="paragraphAlign">段落排列(左对齐,居中,右对齐)</param>
+        /// <param name="isStatement">是否在同一段落创建第二个文本对象(解决同一段落里面需要填充两个或者多个文本值的情况,多个文本需要自己拓展,现在最多支持两个)</param>
+        /// <param name="secondFillContent">第二次声明的文本对象填充的内容,样式与第一次的一致</param>
+        /// <param name="fontColor">字体颜色--十六进制</param>
+        /// <param name="isItalic">是否设置斜体(字体倾斜)</param>
+        /// <returns></returns>
+        internal XWPFParagraph SetParagraph(XWPFDocument document, string fillContent, bool isBold, int fontSize, string fontFamily, ParagraphAlignment paragraphAlign, bool isStatement = false, string secondFillContent = "", string fontColor = "000000", bool isItalic = false)
+        {
+            XWPFParagraph paragraph = document.CreateParagraph();           //创建段落对象
+            paragraph.Alignment = paragraphAlign;                           //文字显示位置,段落排列(左对齐,居中,右对齐)
+            XWPFRun xwpfRun = paragraph.CreateRun();                        //创建段落文本对象
+            xwpfRun.IsBold = isBold;                                        //文字加粗
+            xwpfRun.SetText(fillContent);                                   //填充内容
+            xwpfRun.FontSize = fontSize;                                    //设置文字大小
+            xwpfRun.IsItalic = isItalic;                                    //是否设置斜体(字体倾斜)
+            xwpfRun.SetColor(fontColor);                                    //设置字体颜色--十六进制
+            xwpfRun.SetFontFamily(fontFamily, FontCharRange.None);          //设置标题样式如:(微软雅黑,隶书,楷体)根据自己的需求而定
+
+            if (!isStatement) return paragraph;
+
+            XWPFRun secondXwpfRun = paragraph.CreateRun();                  //创建段落文本对象
+            secondXwpfRun.IsBold = isBold;                                  //文字加粗
+            secondXwpfRun.SetText(secondFillContent);                       //填充内容
+            secondXwpfRun.FontSize = fontSize;                              //设置文字大小
+            secondXwpfRun.IsItalic = isItalic;                              //是否设置斜体(字体倾斜)
+            secondXwpfRun.SetColor(fontColor);                              //设置字体颜色--十六进制
+            secondXwpfRun.SetFontFamily(fontFamily, FontCharRange.None);    //设置标题样式如:(微软雅黑,隶书,楷体)根据自己的需求而定
+
+            return paragraph;
+        }
+
+        /// <summary> 
+        /// 创建Word文档中表格段落实例和设置表格段落文本的基本样式(字体大小,字体,字体颜色,字体对齐位置)
+        /// </summary> 
+        /// <param name="document">document文档对象</param> 
+        /// <param name="table">表格对象</param> 
+        /// <param name="fillContent">要填充的文字</param> 
+        /// <param name="paragraphAlign">段落排列(左对齐,居中,右对齐)</param>
+        /// <param name="textPosition">设置文本位置(设置两行之间的行间,从而实现表格文字垂直居中的效果),从而实现table的高度设置效果 </param>
+        /// <param name="isBold">是否加粗(true加粗,false不加粗)</param>
+        /// <param name="fontSize">字体大小</param>
+        /// <param name="fontColor">字体颜色--十六进制</param>
+        /// <param name="isItalic">是否设置斜体(字体倾斜)</param>
+        /// <returns></returns> 
+        internal XWPFParagraph SetTableParagraph(XWPFDocument document, XWPFTable table, string fillContent, ParagraphAlignment paragraphAlign, int textPosition = 24, bool isBold = false, int fontSize = 10, string fontColor = "000000", bool isItalic = false)
+        {
+            var para = new CT_P();
+
+            //设置单元格文本对齐
+            para.AddNewPPr().AddNewTextAlignment();
+
+            XWPFParagraph paragraph = new XWPFParagraph(para, table.Body);          //创建表格中的段落对象
+            paragraph.Alignment = paragraphAlign;                                   //文字显示位置,段落排列(左对齐,居中,右对齐)
+            //paragraph.FontAlignment =Convert.ToInt32(ParagraphAlignment.CENTER);  //字体在单元格内显示位置与 paragraph.Alignment效果相似
+
+            XWPFRun xwpfRun = paragraph.CreateRun();                                //创建段落文本对象
+            xwpfRun.SetText(fillContent);
+            xwpfRun.FontSize = fontSize;                                            //字体大小
+            xwpfRun.SetColor(fontColor);                                            //设置字体颜色--十六进制
+            xwpfRun.IsItalic = isItalic;                                            //是否设置斜体(字体倾斜)
+            xwpfRun.IsBold = isBold;                                                //是否加粗
+            xwpfRun.SetFontFamily("宋体", FontCharRange.None);                      //设置字体(如:微软雅黑,华文楷体,宋体)
+            //xwpfRun.SetTextPosition(textPosition);                                //设置文本位置(设置两行之间的行间),从而实现table的高度设置效果 
+            return paragraph;
+        }
+
+        public abstract void Write(List<EquipmentTask> tasks, string savePath);
+    }
+}

+ 30 - 0
SHJX.Service.Common/Extend/ObjectConvert.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Linq;
+using System.Windows.Data;
+
+namespace SHJX.Service.Common.Extend
+{
+    /// <summary>
+    /// CommandParameter 多参数传递
+    /// </summary>
+    public class ObjectConvert : IMultiValueConverter
+    {
+        #region IMultiValueConverter Members
+
+        public static object ConverterObject;
+
+        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            ConverterObject = values;
+            var str = values.GetType().ToString();
+            return values.ToArray();
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion
+    }
+}

+ 92 - 0
SHJX.Service.Common/ExtendElement/PasswordHelper.cs

@@ -0,0 +1,92 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SHJX.Service.Common.ExtendElement
+{
+    public static class PasswordHelper
+    {
+        public static readonly DependencyProperty PasswordProperty =
+            DependencyProperty.RegisterAttached("Password",
+            typeof(string), typeof(PasswordHelper),
+            new FrameworkPropertyMetadata(string.Empty, OnPasswordPropertyChanged));
+
+        public static readonly DependencyProperty AttachProperty =
+            DependencyProperty.RegisterAttached("Attach",
+            typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, Attach));
+
+        private static readonly DependencyProperty IsUpdatingProperty =
+           DependencyProperty.RegisterAttached("IsUpdating", typeof(bool),
+           typeof(PasswordHelper));
+
+
+        public static void SetAttach(DependencyObject dp, bool value)
+        {
+            dp.SetValue(AttachProperty, value);
+        }
+
+        public static bool GetAttach(DependencyObject dp)
+        {
+            return (bool)dp.GetValue(AttachProperty);
+        }
+
+        public static string GetPassword(DependencyObject dp)
+        {
+            return (string)dp.GetValue(PasswordProperty);
+        }
+
+        public static void SetPassword(DependencyObject dp, string value)
+        {
+            dp.SetValue(PasswordProperty, value);
+        }
+
+        private static bool GetIsUpdating(DependencyObject dp)
+        {
+            return (bool)dp.GetValue(IsUpdatingProperty);
+        }
+
+        private static void SetIsUpdating(DependencyObject dp, bool value)
+        {
+            dp.SetValue(IsUpdatingProperty, value);
+        }
+
+        private static void OnPasswordPropertyChanged(DependencyObject sender,
+            DependencyPropertyChangedEventArgs e)
+        {
+            if (sender is PasswordBox passwordBox)
+            {
+                passwordBox.PasswordChanged -= PasswordChanged;
+
+                if (!GetIsUpdating(passwordBox))
+                {
+                    passwordBox.Password = (string)e.NewValue;
+                }
+                passwordBox.PasswordChanged += PasswordChanged;
+            }
+        }
+
+        private static void Attach(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+        {
+            if (sender is not PasswordBox passwordBox)
+                return;
+            if ((bool)e.OldValue)
+            {
+                passwordBox.PasswordChanged -= PasswordChanged;
+            }
+
+            if ((bool)e.NewValue)
+            {
+                passwordBox.PasswordChanged += PasswordChanged;
+            }
+        }
+
+        private static void PasswordChanged(object sender, RoutedEventArgs e)
+        {
+            if (sender is PasswordBox passwordBox)
+            {
+                SetIsUpdating(passwordBox, true);
+                SetPassword(passwordBox, passwordBox.Password);
+                SetIsUpdating(passwordBox, false);
+            }
+        }
+    }
+}

+ 61 - 0
SHJX.Service.Common/ExtendElement/UMessageBox.cs

@@ -0,0 +1,61 @@
+using System.Windows;
+using Panuon.UI.Silver;
+using Panuon.UI.Silver.Core;
+
+namespace SHJX.Service.Common.ExtendElement
+{
+    public class UMessageBox
+    {
+        public static void Error(string message)
+        {
+            MessageBoxX.Show(message, "Error", Application.Current.MainWindow, MessageBoxButton.OK, new MessageBoxXConfigurations()
+            {
+                MessageBoxStyle = MessageBoxStyle.Modern,
+                MessageBoxIcon = MessageBoxIcon.None,
+                ButtonBrush = "#FF4C4C".ToColor().ToBrush(),
+            });
+        }
+
+        public static MessageBoxResult Info(string Message)
+        {
+            var configuras = new MessageBoxXConfigurations
+            {
+                MinHeight = 300,
+                MaxContentHeight = 300,
+                MessageBoxStyle = MessageBoxStyle.Modern,
+                MessageBoxIcon = MessageBoxIcon.None
+            };
+            var result = MessageBoxX.Show(Message, "提示", Application.Current.MainWindow, MessageBoxButton.YesNo, configurations: configuras);
+            return result;
+        }
+        public static MessageBoxResult SingleBtnInfo(string Message)
+        {
+            var configuras = new MessageBoxXConfigurations
+            {
+                MinHeight = 300,
+                MaxContentHeight = 300,
+                MessageBoxStyle = MessageBoxStyle.Modern,
+                MessageBoxIcon = MessageBoxIcon.None
+            };
+            var result = MessageBoxX.Show(Message, "提示", Application.Current.MainWindow, MessageBoxButton.OK, configurations: configuras);
+            return result;
+        }
+
+        public static void InfoTip(string message)
+        {
+            Notice.Show(message, "Info", 5, MessageBoxIcon.Info);
+        }
+
+        public static void SuccessTip(string message)
+        {
+            Notice.Show(message, "Success", 5, MessageBoxIcon.Success);
+        }
+
+        public static void ErrorTip(string message)
+        {
+            Notice.Show(message, "Error", 5, MessageBoxIcon.Error);
+        }
+
+
+    }
+}

+ 7 - 0
SHJX.Service.Common/Interface/InterceptorImp.cs

@@ -0,0 +1,7 @@
+namespace SHJX.Service.Common.Interface
+{
+    public  interface InterceptorImp
+    {
+        object Invoke(object @object, string @method, object[] parameters);
+    }
+}

+ 53 - 0
SHJX.Service.Common/Logging/LogFactory.cs

@@ -0,0 +1,53 @@
+using System;
+using Serilog;
+using System.IO;
+using Serilog.Events;
+using Serilog.Enrichers;
+using Serilog.Formatting.Compact;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Configuration;
+
+namespace SHJX.Service.Common.Logging
+{
+    public static class LogFactory
+    {
+        public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
+        .SetBasePath(Directory.GetCurrentDirectory())//设置基础路径
+        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)//添加配置文件
+        .AddEnvironmentVariables()//添加环境变量
+        .Build();
+
+        /// <summary>
+        /// 初始化LogFactory的静态成员.
+        /// </summary>
+        static LogFactory()
+        {
+            //存储日志文件的格式
+            string SerilogOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} - {ThreadId} - [{Level:u3}] - [{SourceContext}] ----- {Message:lj}{NewLine}{Exception}";
+            //存储日志文件的路径
+            static string LogFilePath(string LogEvent) => $@"{AppContext.BaseDirectory}Logs\{LogEvent}\log.log";
+            Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration)
+            .MinimumLevel.Debug()
+            .Enrich.FromLogContext()//使用Serilog.Context.LogContext中的属性丰富日志事件。
+            .Enrich.With<ThreadIdEnricher>()
+            .WriteTo.Console(new RenderedCompactJsonFormatter())
+            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level.Equals(LogEventLevel.Debug)).WriteTo.File(LogFilePath("Debug"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))
+            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level.Equals(LogEventLevel.Information)).WriteTo.File(LogFilePath("Information"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))
+            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level.Equals(LogEventLevel.Warning)).WriteTo.File(LogFilePath("Warning"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))
+            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level.Equals(LogEventLevel.Error)).WriteTo.File(LogFilePath("Error"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))
+            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level.Equals(LogEventLevel.Fatal)).WriteTo.File(LogFilePath("Fatal"), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate))
+            .CreateLogger();
+            BuildLogger = (type) =>
+            {
+                Microsoft.Extensions.Logging.ILogger logger = new LoggerFactory().AddSerilog(Log.Logger).CreateLogger(type.FullName);
+                return logger;
+            };
+        }
+
+        /// <summary>
+        /// <para>获取或设置已配置记录器的日志生成器。</para>
+        /// <para>应该调用它来返回一个新的日志实例。</para>
+        /// </summary>
+        public static Func<Type, Microsoft.Extensions.Logging.ILogger> BuildLogger { get; set; }
+    }
+}

+ 150 - 0
SHJX.Service.Common/Logging/ReadXML/AbstractConfiguration.cs

@@ -0,0 +1,150 @@
+
+using System;
+using System.Xml;
+using System.Reflection;
+using System.Collections.Generic;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+namespace SHJX.Service.Common.ReadXML
+{
+    /// <summary>
+    /// 抽象配置类。默认使用一个xml文件保存配置信息。每个具体的实现类中应该从该xml的不同节点获取相应的配置信息。
+    /// </summary>
+    public abstract class AbstractConfiguration
+    {
+        //private static readonly ILogger logger = LogFactory.BuildLogger(typeof(AbstractConfiguration));
+        private XmlDocument configFile;
+        private static string fileName;
+        public void Load(string source)
+        {
+            fileName = source;
+            configFile = new XmlDocument();
+            var settings = new XmlReaderSettings
+            {
+                IgnoreComments = true //忽略文档里面的注释
+            };
+            var reader = XmlReader.Create(source, settings);
+            configFile.Load(reader);
+        }
+
+        /// <summary>
+        /// 无参构造
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        public T ConvertTo<T>()
+        {
+            var type = typeof(T);
+            if (type.BaseType != typeof(AbstractConfiguration))
+                return default;
+            var obj = Activator.CreateInstance(type, true);
+            var baseObj = (AbstractConfiguration)obj;
+            baseObj.configFile = configFile;
+            return (T)obj;
+        }
+
+        /// <summary>
+        /// 有参构造
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="args"></param>
+        /// <returns></returns>
+        public T ConvertTo<T>(object[] args)
+        {
+            var type = typeof(T);
+            if (type.BaseType != typeof(AbstractConfiguration))
+                return default;
+            var obj = Activator.CreateInstance(type, args);
+            var baseObj = (AbstractConfiguration)obj;
+            baseObj.configFile = this.configFile;
+            return (T)obj;
+        }
+
+        /// <summary>
+        /// 获取单值
+        /// </summary>
+        /// <param name="xmlPath">节点地址</param>
+        /// <returns></returns>
+        protected string ReadSingleValue(string xmlPath)
+        {
+            var xn = configFile.SelectSingleNode(xmlPath);
+            if (xn != null)
+            {
+                switch (xn.NodeType)
+                {
+                    case XmlNodeType.Attribute:
+                        return xn.Value;
+                    case XmlNodeType.Element:
+                        return xn.InnerText;
+                }
+            }
+
+            //logger.LogError("Failed to find an available value using xPath {0}. ", xmlPath);
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// 获取值的集合
+        /// </summary>
+        /// <param name="xmlPath"></param>
+        /// <returns></returns>
+        protected XmlNodeList ReadListValue(string xmlPath) =>
+            configFile.SelectNodes(xmlPath);
+
+        /// <summary>
+        /// 获取字典集合
+        /// </summary>
+        /// <param name="xmlPath"></param>
+        /// <returns></returns>
+        protected IEnumerable<Dictionary<string, string>> ReadSetValue(string xmlPath)
+        {
+            var setValues = new List<Dictionary<string, string>>();
+            var xnList = ReadListValue(xmlPath);
+            if (xnList != null)
+            {
+                for (int i = 0; i < xnList.Count; i++)
+                {
+                    var xn = xnList[i];
+                    var item = new Dictionary<string, string>();
+                    var xaCollection = xn.Attributes;
+                    if (xaCollection is not null)
+                        for (var j = 0; j < xaCollection.Count; j++)
+                        {
+                            item.Add(xaCollection[j].Name, xaCollection[j].Value);
+                        }
+
+                    setValues.Add(item);
+                }
+            }
+            else
+            {
+                //logger.LogInformation("Cannot find any set definition using path [{0}].", xmlPath);
+            }
+
+            return setValues;
+        }
+
+        /// <summary>
+        /// 写入单值
+        /// </summary>
+        /// <param name="xmlPath"></param>
+        /// <param name="value"></param>
+        protected void WriteSingleValue(string xmlPath, object value)
+        {
+            var xn = configFile.SelectSingleNode(xmlPath);
+            if (xn != null)
+            {
+                switch (xn.NodeType)
+                {
+                    case XmlNodeType.Attribute:
+                        xn.Value = value.ToString();
+                        break;
+                    case XmlNodeType.Element:
+                        xn.InnerText = value.ToString();
+                        break;
+                }
+            }
+            configFile.Save(fileName);
+        }
+    }
+}

+ 19 - 0
SHJX.Service.Common/Logging/ReadXML/CreateConfig.cs

@@ -0,0 +1,19 @@
+using System;
+
+namespace SHJX.Service.Common.ReadXML
+{
+    public static class CreateConfig
+    {
+        private static string _opKey;
+        private static string ConfigFileName => string.Concat(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "conf/", _opKey, ".config.xml");
+
+        public static ReadConfigUtil CreateReadConfigUtil(string opKey)
+        {
+            _opKey = opKey;
+            var opConfig = new ReadConfigUtil();
+            opConfig.Load(ConfigFileName);
+            var config = opConfig.ConvertTo<ReadConfigUtil>();
+            return config;
+        }
+    }
+}

+ 141 - 0
SHJX.Service.Common/Logging/ReadXML/ReadConfigUtil.cs

@@ -0,0 +1,141 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using SHJX.Service.Common.Model;
+using SHJX.Service.Model.Control;
+
+namespace SHJX.Service.Common.ReadXML
+{
+    public class ReadConfigUtil : AbstractConfiguration
+    {
+        #region Read XML
+        public string ConnStr => ReadSingleValue("/Equipment/data-source/connstr/@value");
+        public int Baudrate => Convert.ToInt32(ReadSingleValue("/Equipment/modbus-config/modbus-baudrate/@value"));
+        public string Port => ReadSingleValue("/Equipment/modbus-config/modbus-port/@port");
+        public bool IsAutoGetPort => Convert.ToBoolean(ReadSingleValue("/Equipment/modbus-config/modbus-port/@isAuto"));
+        public bool OpenAppStartHeat => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/WhenSystemStartBeginHeating/@value"));
+        public bool TaskRunning => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/TaskRunning/@value"));
+        public bool ManiGrabFeedBack => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/ManiGrabFeedBack/@value"));
+        public bool DripNozzleFeedBack => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/DripNozzleFeedBack/@value"));
+        public bool IsAddCupFeedBack => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/IsAddCupFeedBack/@value"));
+        public bool IsAddCupBiaoDingBubei => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/IsAddCupBiaoDingBubei/@value"));
+        public string TemplateFilePath => ReadSingleValue("/Equipment/SettingConfig/TemplateFilePath/@value");
+        public bool SpeedWrite => Convert.ToBoolean(ReadSingleValue("/Equipment/modbus-config/MotorSpeeds/@write"));
+        public string ResultWordFilePath => ReadSingleValue("/Equipment/SettingConfig/ResultWordFilePath/@value");
+        public bool ExportTypeByExcel => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/ExportTypeByExcel/@value"));
+        public bool ExportTypeByWord => Convert.ToBoolean(ReadSingleValue("/Equipment/SettingConfig/ExportTypeByWord/@value"));
+        public double PreheatTemperature => Convert.ToDouble(ReadSingleValue("/Equipment/SettingConfig/PreheatTemperature/@value"));
+        public double AddWaterTemperature => Convert.ToDouble(ReadSingleValue("/Equipment/SettingConfig/AddWaterTemperature/@value"));
+        public double CoolingPipeWatingTime => Convert.ToDouble(ReadSingleValue("/Equipment/SettingConfig/CoolingPipeWatingTime/@value"));
+        public double DissolveMoveDistance => Convert.ToDouble(ReadSingleValue("/Equipment/SettingConfig/DissolveMoveDistance/@value"));
+        public double PotassiumDichromateValue => Convert.ToDouble(ReadSingleValue("/Equipment/DropLiquidAmounts/Liquid[@Name='PotassiumDichromate']/@Value"));
+        public double FASValue => Convert.ToDouble(ReadSingleValue("/Equipment/DropLiquidAmounts/Liquid[@Name='FAS']/@LowValue"));
+        public List<MotorSpeed> GetSpeed => GetMotorSpeed("/Equipment/modbus-config/MotorSpeeds/Speed");
+        public Dictionary<string, byte> ModBusNodeIDs => GetBodbusNodeId("/Equipment/modbus-config/ModbusNodeID/NodeId");
+        public List<FlowControlProxyArg> GetResponsibility => GetNameAndType("/Equipment/responsibilitys/responsibility");
+
+        public List<DropAmount> DropOnceAmounts => GetAmount("/Equipment/DropLiquidAmounts/Liquid");
+
+        public string LogiCamera1 => ReadSingleValue("/Equipment/SettingConfig/LogiCamera1/@value");
+        public string LogiCamera2 => ReadSingleValue("/Equipment/SettingConfig/LogiCamera2/@value");
+        public int GetPositionDistance(string Name)
+        {
+            string value = ReadSingleValue("/Equipment/Positions/Position[@Name='" + Name + "']/@Value");
+            string convert = ReadSingleValue("/Equipment/Positions/Position[@Name='" + Name + "']/@Convert");
+            return Convert.ToInt32(Math.Round(Convert.ToDouble(value) * Convert.ToDouble(convert)));
+        }
+
+        private Dictionary<string, byte> GetBodbusNodeId(string xmlpath)
+        {
+            var definitions = ReadSetValue(xmlpath);
+            return definitions.ToDictionary(item => item["EquipmentName"], item => Convert.ToByte(item["NodeID"]));
+        }
+
+        private List<DropAmount> GetAmount(string xmlpath)
+        {
+            List<DropAmount> amounts = new();
+            var definitions = ReadSetValue(xmlpath);
+            foreach (var item in definitions)
+            {
+                amounts.Add(new DropAmount()
+                {
+                    Name = item["Name"],
+                    Description = item["Description"],
+                    LowValue = Convert.ToDouble(item["LowValue"]),
+                    HighValue = Convert.ToDouble(item["HighValue"])
+                });
+            }
+            return amounts;
+        }
+
+        public List<MotorSpeed> GetMotorSpeed(string xmlpath)
+        {
+            List<MotorSpeed> speeds = new();
+            var definitions = ReadSetValue(xmlpath);
+            foreach (var item in definitions)
+            {
+                speeds.Add(new MotorSpeed
+                {
+                    NodeName = item["NodeName"],
+                    NodeId = item["NodeID"],
+                    Speed = Convert.ToInt32(item["Speed"]),
+                    AcSpeed = Convert.ToInt32(item["AC"]),
+                    DeSpeed = Convert.ToInt32(item["DE"]),
+                    Description = item["Description"]
+                });
+            }
+            return speeds;
+        }
+
+        private List<FlowControlProxyArg> GetNameAndType(string xmlpath)
+        {
+            var proxyArgs = new List<FlowControlProxyArg>();
+            var definitions = ReadSetValue(xmlpath);
+            foreach (var item in definitions)
+            {
+                proxyArgs.Add(new FlowControlProxyArg()
+                {
+                    Name = item["name"],
+                    Flow = item["type"],
+                    Proxy = item["proxy"]
+                });
+            }
+            return proxyArgs;
+        }
+        #endregion
+
+        #region Write XML
+        public void UpdateAppStartHeat(string value) => WriteSingleValue("/Equipment/SettingConfig/WhenSystemStartBeginHeating/@value", value);
+        public void UpdateTaskRunning(object value) => WriteSingleValue("/Equipment/SettingConfig/TaskRunning/@value", value);
+        public void UpdateManiGrabFeedBack(object value) => WriteSingleValue("/Equipment/SettingConfig/ManiGrabFeedBack/@value", value);
+        public void UpdateDripNozzleFeedBack(object value) => WriteSingleValue("/Equipment/SettingConfig/DripNozzleFeedBack/@value", value);
+        public void UpdateIsAddCupFeedBack(object value) => WriteSingleValue("/Equipment/SettingConfig/IsAddCupFeedBack/@value", value);
+        public void UpdateIsAddCupBiaoDingBubei(object value) => WriteSingleValue("/Equipment/SettingConfig/IsAddCupBiaoDingBubei/@value", value);
+        public void UpdateTemplateFilePath(string value) => WriteSingleValue("/Equipment/SettingConfig/TemplateFilePath/@value", value);
+        public void UpdateResultWordFilePath(string value) => WriteSingleValue("/Equipment/SettingConfig/ResultWordFilePath/@value", value);
+
+        public void UpdateExportTypeByExcel(object value) => WriteSingleValue("/Equipment/SettingConfig/ExportTypeByExcel/@value", value);
+        public void UpdateExportTypeByWord(object value) => WriteSingleValue("/Equipment/SettingConfig/ExportTypeByWord/@value", value);
+
+        public void UpdatePreheatTemperature(object value) => WriteSingleValue("/Equipment/SettingConfig/PreheatTemperature/@value", value);
+        public void UpdateAddWaterTemperature(object value) => WriteSingleValue("/Equipment/SettingConfig/AddWaterTemperature/@value", value);
+
+        public void UpdateDissolveMoveDistance(object value) => WriteSingleValue("/Equipment/SettingConfig/DissolveMoveDistance/@value", value);
+        public void UpdateCoolingPipeWatingTime(object value) => WriteSingleValue("/Equipment/SettingConfig/CoolingPipeWatingTime/@value", value);
+        public void UpdateSpeedWrite(object value) => WriteSingleValue("/Equipment/modbus-config/MotorSpeeds/@write", value);
+        public void UpdateSpeed(string node, object value) => WriteSingleValue("/Equipment/modbus-config/MotorSpeeds/Speed[@NodeID='" + node + "']/@Speed", value);
+        public void UpdateAcSpeed(string node, object value) => WriteSingleValue("/Equipment/modbus-config/MotorSpeeds/Speed[@NodeID='" + node + "']/@AC", value);
+        public void UpdateDeSpeed(string node, object value) => WriteSingleValue("/Equipment/modbus-config/MotorSpeeds/Speed[@NodeID='" + node + "']/@DE", value);
+        public void UpdatePort(object value) => WriteSingleValue("/Equipment/modbus-config/modbus-port/@port", value);
+        public void UpdateAutoFindPort(object value) => WriteSingleValue("/Equipment/modbus-config/modbus-port/@isAuto",value);
+        public void UpdateAmount(List<DropAmount> amounts)
+        {
+            amounts.ForEach(item =>
+            {
+                WriteSingleValue("/Equipment/DropLiquidAmounts/Liquid[@Name='" + item.Name + "']/@LowValue", item.LowValue);
+                WriteSingleValue("/Equipment/DropLiquidAmounts/Liquid[@Name='" + item.Name + "']/@HighValue", item.HighValue);
+            });
+        }
+        #endregion
+    }
+}

+ 17 - 0
SHJX.Service.Common/Model/FlowControlProxyArg.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SHJX.Service.Common.Model
+{
+    public class FlowControlProxyArg
+    {
+        public string Name { get; set; }
+
+        public string Flow { get; set; }
+
+        public string Proxy { get; set; }
+    }
+}

+ 45 - 0
SHJX.Service.Common/Model/LiquidDropArgs.cs

@@ -0,0 +1,45 @@
+using SHJX.Service.Common.Camera;
+
+namespace SHJX.Service.Common.Model
+{
+    public class LiquidDropArgs
+    {
+        /// <summary>
+        /// 照相机
+        /// </summary>
+        public WebCamera Camera { get; set; }
+        /// <summary>
+        /// 图像采集器
+        /// </summary>
+        public ColorDataGraber Graber { get; set; }
+        /// <summary>
+        /// 采样周期
+        /// </summary>
+        public int SignalTimeLen { get; set; }
+        /// <summary>
+        /// 阈值
+        /// </summary>
+        public double BalanceVal { get; set; }
+
+        /// <summary>
+        /// 最大滴定体积(ml)
+        /// </summary>
+        public int MaxDropValue { get; set; }
+
+        public LiquidDropArgs()
+        {
+            SignalTimeLen = 1200;
+            BalanceVal = -5;
+            MaxDropValue = 25;
+        }
+
+        public LiquidDropArgs(WebCamera camera, ColorDataGraber graber)
+        {
+            Camera = camera;
+            Graber = graber;
+            SignalTimeLen = 1200;
+            BalanceVal = -5;
+            MaxDropValue = 25;
+        }
+    }
+}

+ 36 - 0
SHJX.Service.Common/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("SHJX.Service.Common")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SHJX.Service.Common")]
+[assembly: AssemblyCopyright("Copyright ©  2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("0410053a-af47-4ca4-8659-bfbbaeced094")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
SHJX.Service.Common/Proxy/Proxy.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace SHJX.Service.Common.Proxy
+{
+    /// <summary>
+    /// 代理类
+    /// </summary>
+    public static class Proxy
+    {
+        public static object NewProxyInstance(Type realObj, object proxyObj)
+        {
+            string nameOfAssembly = realObj.Name + "ProxyAssembly";
+            string nameOfModule = realObj.Name + "ProxyModule";
+            string nameOfType = realObj.Name + "Proxy";
+            var assemblyName = new AssemblyName(nameOfAssembly);
+            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
+            var moduleBuilder = assembly.DefineDynamicModule(nameOfModule);
+            var typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public | TypeAttributes.NotPublic, realObj);
+            InjectInterceptor(realObj, typeBuilder, proxyObj);
+            var t = typeBuilder.CreateType();
+            return Activator.CreateInstance(t);
+        }
+
+        private static void InjectInterceptor(Type type, TypeBuilder typeBuilder, object proxy)
+        {
+            var fieldInterceptor = typeBuilder.DefineField("_interceptor", proxy.GetType(), FieldAttributes.Private);
+            var constructorBuilder =
+                typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
+            var ilOfCtor = constructorBuilder.GetILGenerator();
+            ilOfCtor.Emit(OpCodes.Ldarg_0);
+            ilOfCtor.Emit(OpCodes.Newobj, proxy.GetType().GetConstructor(Array.Empty<Type>())!);
+            ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor);
+            ilOfCtor.Emit(OpCodes.Ret);
+            var methodsOfType = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
+            foreach (var method in methodsOfType)
+            {
+                var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
+                var attr = MethodAttributes.Public | MethodAttributes.Virtual;
+                var methodBuilder = typeBuilder.DefineMethod(method.Name, attr, CallingConventions.Standard,
+                    method.ReturnType, methodParameterTypes);
+                var ilOfMethod = methodBuilder.GetILGenerator();
+                ilOfMethod.Emit(OpCodes.Ldarg_0);
+                ilOfMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
+                ilOfMethod.Emit(OpCodes.Newobj, type.GetConstructor(Array.Empty<Type>())!);
+                ilOfMethod.Emit(OpCodes.Ldstr, method.Name);
+                var parameters = ilOfMethod.DeclareLocal(typeof(object[]));
+                ilOfMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length);
+                ilOfMethod.Emit(OpCodes.Newarr, typeof(object));
+                ilOfMethod.Emit(OpCodes.Stloc, parameters);
+                for (var j = 0; j < methodParameterTypes.Length; j++)
+                {
+                    ilOfMethod.Emit(OpCodes.Ldloc, parameters);
+                    ilOfMethod.Emit(OpCodes.Ldc_I4, j);
+                    ilOfMethod.Emit(OpCodes.Ldarg, j + 1);
+                    ilOfMethod.Emit(OpCodes.Stelem_Ref);
+                }
+                ilOfMethod.Emit(OpCodes.Ldloc, parameters);
+                ilOfMethod.Emit(OpCodes.Callvirt, proxy.GetType().GetMethod("Invoke")!);
+                if (method.ReturnType == typeof(void))
+                {
+                    ilOfMethod.Emit(OpCodes.Pop);
+                }
+
+                ilOfMethod.Emit(OpCodes.Ret);
+            }
+        }
+    }
+}

+ 355 - 0
SHJX.Service.Common/SHJX.Service.Common.csproj

@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{0410053A-AF47-4CA4-8659-BFBBAECED094}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>SHJX.Service.Common</RootNamespace>
+    <AssemblyName>SHJX.Service.Common</AssemblyName>
+    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+    <TargetFrameworkProfile />
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <LangVersion>preview</LangVersion>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <LangVersion>preview</LangVersion>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>false</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>
+    </AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>preview</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>preview</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="AForge">
+      <HintPath>..\SHJX.Service.Resourece\Lib\AForge.dll</HintPath>
+    </Reference>
+    <Reference Include="AForge.Controls">
+      <HintPath>..\SHJX.Service.Resourece\Lib\AForge.Controls.dll</HintPath>
+    </Reference>
+    <Reference Include="AForge.Imaging">
+      <HintPath>..\SHJX.Service.Resourece\Lib\AForge.Imaging.dll</HintPath>
+    </Reference>
+    <Reference Include="AForge.Math">
+      <HintPath>..\SHJX.Service.Resourece\Lib\AForge.Math.dll</HintPath>
+    </Reference>
+    <Reference Include="AForge.Video">
+      <HintPath>..\SHJX.Service.Resourece\Lib\AForge.Video.dll</HintPath>
+    </Reference>
+    <Reference Include="AForge.Video.DirectShow, Version=2.2.5.0, Culture=neutral, PublicKeyToken=61ea4348d43881b7, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\AForge.Video.DirectShow.2.2.5\lib\AForge.Video.DirectShow.dll</HintPath>
+    </Reference>
+    <Reference Include="BouncyCastle.Crypto, Version=1.8.10.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
+      <HintPath>..\packages\Portable.BouncyCastle.1.8.10\lib\net40\BouncyCastle.Crypto.dll</HintPath>
+    </Reference>
+    <Reference Include="EPPlus, Version=5.6.4.0, Culture=neutral, PublicKeyToken=ea159fdaa78159a1, processorArchitecture=MSIL">
+      <HintPath>..\packages\EPPlus.5.6.4\lib\net45\EPPlus.dll</HintPath>
+    </Reference>
+    <Reference Include="ICSharpCode.SharpZipLib, Version=1.3.1.9, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
+      <HintPath>..\packages\SharpZipLib.1.3.1\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.7.0.0\lib\net462\Microsoft.Extensions.Configuration.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.Configuration.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.Binder, Version=7.0.0.3, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.Binder.7.0.3\lib\net462\Microsoft.Extensions.Configuration.Binder.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.7.0.0\lib\net462\Microsoft.Extensions.Configuration.EnvironmentVariables.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.FileExtensions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.FileExtensions.7.0.0\lib\net462\Microsoft.Extensions.Configuration.FileExtensions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.Json.7.0.0\lib\net462\Microsoft.Extensions.Configuration.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.Xml, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Configuration.Xml.7.0.0\lib\net462\Microsoft.Extensions.Configuration.Xml.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.DependencyModel.7.0.0\lib\net462\Microsoft.Extensions.DependencyModel.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.FileProviders.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.FileProviders.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.FileProviders.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.FileProviders.Physical, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.FileProviders.Physical.7.0.0\lib\net462\Microsoft.Extensions.FileProviders.Physical.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.FileSystemGlobbing, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.FileSystemGlobbing.7.0.0\lib\net462\Microsoft.Extensions.FileSystemGlobbing.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Hosting, Version=7.0.0.1, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Hosting.7.0.1\lib\net462\Microsoft.Extensions.Hosting.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Logging.7.0.0\lib\net462\Microsoft.Extensions.Logging.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging.Console, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Logging.Console.7.0.0\lib\net462\Microsoft.Extensions.Logging.Console.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging.Debug, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Logging.Debug.7.0.0\lib\net462\Microsoft.Extensions.Logging.Debug.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Options, Version=7.0.0.1, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Options.7.0.1\lib\net462\Microsoft.Extensions.Options.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Primitives.7.0.0\lib\net462\Microsoft.Extensions.Primitives.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Office.Interop.Excel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Office.Interop.Excel.15.0.4795.1001\lib\net20\Microsoft.Office.Interop.Excel.dll</HintPath>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+    </Reference>
+    <Reference Include="Microsoft.Office.Interop.Word, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Office.Interop.Word.15.0.4797.1004\lib\net20\Microsoft.Office.Interop.Word.dll</HintPath>
+      <EmbedInteropTypes>False</EmbedInteropTypes>
+    </Reference>
+    <Reference Include="NPOI, Version=2.5.2.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
+      <HintPath>..\packages\NPOI.2.5.2\lib\net45\NPOI.dll</HintPath>
+    </Reference>
+    <Reference Include="NPOI.OOXML, Version=2.5.2.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
+      <HintPath>..\packages\NPOI.2.5.2\lib\net45\NPOI.OOXML.dll</HintPath>
+    </Reference>
+    <Reference Include="NPOI.OpenXml4Net, Version=2.5.2.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
+      <HintPath>..\packages\NPOI.2.5.2\lib\net45\NPOI.OpenXml4Net.dll</HintPath>
+    </Reference>
+    <Reference Include="NPOI.OpenXmlFormats, Version=2.5.2.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
+      <HintPath>..\packages\NPOI.2.5.2\lib\net45\NPOI.OpenXmlFormats.dll</HintPath>
+    </Reference>
+    <Reference Include="Panuon.UI.Core, Version=2.2.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\Panuon.UI.Core.2.2.1\lib\net472\Panuon.UI.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="Panuon.UI.Silver, Version=1.1.3.3, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\Panuon.UI.Silver.1.1.3.3\lib\net45\Panuon.UI.Silver.dll</HintPath>
+    </Reference>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="Prism, Version=8.0.0.1909, Culture=neutral, PublicKeyToken=40ee6c3a2184dc59, processorArchitecture=MSIL">
+      <HintPath>..\packages\Prism.Core.8.0.0.1909\lib\net47\Prism.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.3.0.1\lib\net471\Serilog.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog.Enrichers.Thread, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.Enrichers.Thread.3.2.0-dev-00752\lib\net45\Serilog.Enrichers.Thread.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog.Extensions.Logging, Version=7.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.Extensions.Logging.7.0.0\lib\net462\Serilog.Extensions.Logging.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog.Formatting.Compact, Version=1.1.1.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.Formatting.Compact.1.1.1-dev-00944\lib\net452\Serilog.Formatting.Compact.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog.Settings.Configuration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.Settings.Configuration.7.0.0\lib\net462\Serilog.Settings.Configuration.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog.Sinks.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.Sinks.Console.4.1.1-dev-00896\lib\net45\Serilog.Sinks.Console.dll</HintPath>
+    </Reference>
+    <Reference Include="Serilog.Sinks.File, Version=5.0.1.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\packages\Serilog.Sinks.File.5.0.1-dev-00947\lib\net45\Serilog.Sinks.File.dll</HintPath>
+    </Reference>
+    <Reference Include="SqlSugar, Version=5.0.2.6, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\SqlSugar.5.0.2.6\lib\SqlSugar.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ComponentModel.Annotations, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ComponentModel.Annotations.4.7.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ComponentModel.Composition" />
+    <Reference Include="System.ComponentModel.DataAnnotations" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Diagnostics.DiagnosticSource, Version=7.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Diagnostics.DiagnosticSource.7.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
+      <Private>True</Private>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
+      <Private>True</Private>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Runtime.Caching" />
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
+      <Private>True</Private>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Security" />
+    <Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
+      <Private>True</Private>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
+      <Private>True</Private>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
+      <Private>True</Private>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Security.Cryptography.Xml, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Security.Cryptography.Xml.7.0.0\lib\net462\System.Security.Cryptography.Xml.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encodings.Web.7.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Json.7.0.0\lib\net462\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks" />
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\packages\Expression.Blend.Sdk.1.0.2\lib\net45\System.Windows.Interactivity.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Calculate\SampleCalculate.cs" />
+    <Compile Include="Camera\ColorDataGrabedEventArgs.cs" />
+    <Compile Include="Camera\ColorDataGraber.cs" />
+    <Compile Include="Camera\ColorDataPoint.cs" />
+    <Compile Include="Camera\CreatGraber.cs" />
+    <Compile Include="Camera\ImageRGBanalysis.cs" />
+    <Compile Include="Camera\NoiseDataFilter.cs" />
+    <Compile Include="Camera\SensorsParameter.cs" />
+    <Compile Include="Camera\TitrationSettings.cs" />
+    <Compile Include="Camera\WebCamera.cs" />
+    <Compile Include="CustomUtil\DataCache.cs" />
+    <Compile Include="ExtendElement\UMessageBox.cs" />
+    <Compile Include="Extend\CancelCache.cs" />
+    <Compile Include="Extend\Encryption.cs" />
+    <Compile Include="Model\FlowControlProxyArg.cs" />
+    <Compile Include="Model\LiquidDropArgs.cs" />
+    <Compile Include="Extend\ObjectConvert.cs" />
+    <Compile Include="ExtendElement\PasswordHelper.cs" />
+    <Compile Include="Interface\InterceptorImp.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Logging\LogFactory.cs" />
+    <Compile Include="UserColor\UColor.cs" />
+    <Compile Include="Extend\ExtendUtil.cs" />
+    <Compile Include="Extend\Converter.cs" />
+    <Compile Include="Proxy\Proxy.cs" />
+    <Compile Include="Logging\ReadXML\AbstractConfiguration.cs" />
+    <Compile Include="Logging\ReadXML\CreateConfig.cs" />
+    <Compile Include="Logging\ReadXML\ReadConfigUtil.cs" />
+    <Compile Include="UserDelegate\Messager.cs" />
+    <Compile Include="UserTimer\TimerProcessor.cs" />
+    <Compile Include="Utils\Converts.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="app.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SHJX.Service.Model\SHJX.Service.Model.csproj">
+      <Project>{1b70b432-a96d-4785-96a0-0be6291ede3d}</Project>
+      <Name>SHJX.Service.Model</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <COMReference Include="Microsoft.Office.Core">
+      <Guid>{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}</Guid>
+      <VersionMajor>2</VersionMajor>
+      <VersionMinor>8</VersionMinor>
+      <Lcid>0</Lcid>
+      <WrapperTool>primary</WrapperTool>
+      <Isolated>False</Isolated>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+    </COMReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 3 - 0
SHJX.Service.Common/SHJX.Service.Common.csproj.user

@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+</Project>

+ 28 - 0
SHJX.Service.Common/UserColor/UColor.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Windows.Media;
+
+namespace SHJX.Service.Common.UserColor
+{
+    public static class UColor
+    {
+        public static SolidColorBrush SpringGreen => "#66CC99".ConvertToBrush();
+        public static SolidColorBrush DarkOrange => "#FF9933".ConvertToBrush();
+        public static SolidColorBrush DoderBlue => "#1296DB".ConvertToBrush();
+        public static SolidColorBrush DeepSkyBlue => "#C800B2FF".ConvertToBrush();
+        public static SolidColorBrush SteelBlue => "#2281D1".ConvertToBrush();
+        public static SolidColorBrush LightOrange => "#FF6600".ConvertToBrush();
+        public static SolidColorBrush OrangeRed=> "#FF4500".ConvertToBrush();
+
+        private static readonly BrushConverter BrushConverter = new();
+
+        public static SolidColorBrush ConvertToBrush(this string colorRgb)
+        {
+            if (string.IsNullOrWhiteSpace(colorRgb))
+            {
+                throw new ArgumentNullException(colorRgb);
+            }
+
+            return (SolidColorBrush)BrushConverter.ConvertFromString(colorRgb);
+        }
+    }
+}

+ 37 - 0
SHJX.Service.Common/UserDelegate/Messager.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace SHJX.Service.Common.UserDelegate
+{
+    public static class Messager
+    {
+        private static readonly ConcurrentDictionary<string, Action<object>> Actions = new();
+
+        /// <summary>
+        /// 注册
+        /// </summary>
+        /// <param name="deleName"></param>
+        /// <param name="action"></param>
+        public static void Register(string deleName, Action<object> action)
+        {
+            if (!Actions.ContainsKey(deleName))
+            {
+                Actions.TryAdd(deleName, action);
+            }
+            Actions[deleName] = action;
+        }
+
+        /// <summary>
+        /// 发送
+        /// </summary>
+        /// <param name="opKey"></param>
+        /// <param name="arg"></param>
+        public static void Send(string opKey, object arg = null)
+        {
+            if (Actions.TryGetValue(opKey, out var service))
+            {
+                service?.Invoke(arg);
+            }
+        }
+    }
+}

+ 76 - 0
SHJX.Service.Common/UserTimer/TimerProcessor.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Timers;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+namespace SHJX.Service.Common.UserTimer
+{
+    /// <summary>
+    /// Timer封装类,定义过程统一
+    /// </summary>
+    public class TimerProcessor
+    {
+
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(TimerProcessor));
+        private readonly string _timerName;
+        public event Action OnTimer;
+        private readonly Timer _timer;
+        private bool _timerActive;
+        private int _heartBeat;
+
+        public TimerProcessor(string timerName, double interval)
+        {
+            _timerName = timerName;
+            _timer = new Timer
+            {
+                Interval = interval
+            };
+            _timer.Elapsed += Timer_Elapsed;
+        }
+
+        [DebuggerStepThrough]
+        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if (_timerActive)
+                return;
+            if (++_heartBeat >= int.MaxValue)
+                _heartBeat = 0;
+            var sw = new Stopwatch();
+            sw.Start();
+            try
+            {
+                _timerActive = true;
+                OnTimer?.Invoke();
+            }
+            catch (Exception exception)
+            {
+                logger.LogError("The timer {0} has a exception {1}", _timerName, exception.Message);
+            }
+            finally
+            {
+                sw.Stop();
+                _timerActive = false;
+            }
+        }
+
+        public void Start()
+        {
+            if (_timer.Enabled) return;
+            _timer.Enabled = true;
+            logger.LogDebug("The timer {0} started.", this._timerName);
+        }
+
+        public void Stop()
+        {
+            if (!_timer.Enabled) return;
+            _timer.Enabled = false;
+            logger.LogDebug("The timer {0} stopped.", this._timerName);
+        }
+
+        public double GetInterval()
+        {
+            return _timer.Interval;
+        }
+    }
+}

+ 187 - 0
SHJX.Service.Common/Utils/Converts.cs

@@ -0,0 +1,187 @@
+using System;
+using System.IO;
+using OfficeOpenXml;
+using OfficeOpenXml.Style;
+using SHJX.Service.Model.Control;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Windows.Media;
+using LicenseContext = OfficeOpenXml.LicenseContext;
+
+namespace SHJX.Service.Common.Utils
+{
+    public static class Converts
+    {
+        #region 颜色
+        private static readonly BrushConverter BrushConverter = new();
+        public static SolidColorBrush ConvertToBrush(this string colorRgb)
+        {
+            if (string.IsNullOrWhiteSpace(colorRgb))
+            {
+                throw new ArgumentNullException(colorRgb);
+            }
+
+            return (SolidColorBrush)BrushConverter.ConvertFromString(colorRgb);
+        }
+        #endregion
+
+        #region ExcelConvert
+
+        public static void ConvertToExcel(this DataTable dt, string filePath)
+        {
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+            using var package = new ExcelPackage();
+           
+            var sheet = package.Workbook.Worksheets.Add("SampleTemplate");
+            for (int i = 0; i < dt.Columns.Count; i++)
+            {
+                sheet.Column(i + 1).Width = 15;
+            }
+            sheet.Row(1).Style.Font.Bold = true;
+            sheet.Row(1).Style.Font.Size = 12;
+            sheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
+            sheet.Row(1).Style.VerticalAlignment = ExcelVerticalAlignment.Center;
+            for (var i = 0; i < dt.Columns.Count; i++)
+            {
+                sheet.SetValue(1, i + 1, dt.Columns[i].ColumnName);
+            }
+            string[] types = new string[] { "水样", "空白", "标定"};
+            var typeVaildationData = sheet.DataValidations.AddListValidation("F2:F49");
+            foreach (var item in types)
+            {
+                typeVaildationData.Formula.Values.Add(item);
+            }
+            string[] concentrations = new string[] { "高", "低" };
+            var concentrationVaildationData = sheet.DataValidations.AddListValidation("G2:G49");
+            foreach (var item in concentrations)
+            {
+                concentrationVaildationData.Formula.Values.Add(item);
+            }
+            //数据绑定
+            for (var i = 0; i < dt.Rows.Count; i++)
+            {
+                for (var j = 0; j < dt.Columns.Count; j++)
+                {
+                    sheet.SetValue(i + 2, j + 1, dt.Rows[i][j].GetType().IsValueType ? Convert.ToDouble(dt.Rows[i][j]) : dt.Rows[i][j].ToString());
+                }
+            }
+            byte[] bin = package.GetAsByteArray();
+            File.WriteAllBytes(filePath, bin);
+        }
+
+        /// <summary>
+        /// 读取Excel
+        /// </summary>
+        /// <param name="filePath"></param>
+        /// <param name="hasHeader"></param>
+        /// <returns></returns>
+        public static DataTable ReadExcel(this string filePath, bool hasHeader = true)
+        {
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+            using ExcelPackage package = new();
+            using (FileStream stream = File.OpenRead(filePath))
+            {
+                package.Load(stream);
+            }
+            var ws = package.Workbook.Worksheets.First();
+            DataTable dt = new();
+            foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column])
+            {
+                dt.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column));
+            }
+            var startRow = hasHeader ? 2 : 1;
+            for (var rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++)
+            {
+                var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column];
+                var row = dt.NewRow();
+                foreach (var cell in wsRow)
+                {
+                    row[cell.Start.Column - 1] = cell.Text;
+                }
+                dt.Rows.Add(row);
+            }
+            return dt;
+        }
+        #endregion
+
+        #region ListToDataTable
+        public static DataTable ToDataTable<T>(this List<T> items)
+        {
+            DataTable dataTable = new();
+            PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+            foreach (PropertyInfo prop in Props)
+            {
+                dataTable.Columns.Add(prop.Name);
+            }
+            foreach (T obj in items)
+            {
+                var values = new object[Props.Length];
+                for (int i = 0; i < Props.Length; i++)
+                {
+                    values[i] = Props[i].GetValue(obj, null);
+                }
+                dataTable.Rows.Add(values);
+            }
+            return dataTable;
+        }
+        #endregion
+
+        #region DataTable -> List<T>
+
+        /// <summary>
+        /// 将DataTable转换为List
+        /// </summary>
+        /// <param name="dt"></param>
+        /// <returns></returns>
+        public static List<T> ConvertToList<T>(this DataTable dt) where T : class, new()
+        {
+            List<T> lists = new(); // 定义集合
+            foreach (DataRow dr in dt.Rows) //遍历DataTable中所有的数据行
+            {
+                T t = new();
+                PropertyInfo[] propertys = typeof(T).GetProperties(); // 获得此模型的公共属性
+                foreach (PropertyInfo item in propertys) //遍历该对象的所有属性
+                {
+                    object value = null;
+                    object[] attr = item.GetCustomAttributes(typeof(DescriptionAttribute), false);
+                    string description = attr.Length.Equals(0) ? item.Name : ((DescriptionAttribute)attr[0]).Description;
+                    object[] defaultValues = item.GetCustomAttributes(typeof(DefaultValueAttribute), false);
+                    //检查DataTable是否包含此列(列名等于对象的属性的Description属性)
+                    if (!dt.Columns.Contains(description))
+                    {
+                        if (defaultValues is null or { Length: 0 })
+                        {
+                            continue;
+                        }
+                        value = ((DefaultValueAttribute)defaultValues[0]).Value;
+                    }
+                    else
+                    {
+                        if (!item.CanWrite) continue; //该属性不可写,直接跳出
+                        value = dr[description];//取值
+                    }
+                    if (value.Equals(DBNull.Value))
+                    {
+                        continue;//如果空,则返回
+                    }
+                    value = item.PropertyType.FullName switch
+                    {
+                        "System.String" => value.ToString(),
+                        "System.Double" => Convert.ToDouble(value),
+                        "System.Boolean" => value.Equals("1"),
+                        "System.Int32" => Convert.ToInt32(value),
+                        _ => value,
+                    };
+                    item.SetValue(t, value, null);
+                }
+                lists.Add(t); //对象添加到泛型集合中
+            }
+            return lists;
+        }
+
+        #endregion
+    }
+}

+ 147 - 0
SHJX.Service.Common/app.config

@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-8.0.23.0" newVersion="8.0.23.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="BouncyCastle.Crypto" publicKeyToken="0e99375e54769942" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.8.10.0" newVersion="1.8.10.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="ICSharpCode.SharpZipLib" publicKeyToken="1b03e6acf1164f73" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.3.1.9" newVersion="1.3.1.9" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Google.Protobuf" publicKeyToken="a7d26565bac4d604" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-3.15.1.0" newVersion="3.15.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="K4os.Compression.LZ4.Streams" publicKeyToken="2186fa9121ef231d" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.2.10.0" newVersion="1.2.10.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Renci.SshNet" publicKeyToken="1cee9f8bde3db106" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-2020.0.1.0" newVersion="2020.0.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Options" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.1" newVersion="7.0.0.1" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.DependencyInjection" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.1" newVersion="7.0.0.1" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Options.ConfigurationExtensions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.Binder" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.3" newVersion="7.0.0.3" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Primitives" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.FileProviders.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.Configuration" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.FileProviders.Physical" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.EnvironmentVariables" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.UserSecrets" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.0.1" newVersion="6.0.0.1" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.EventLog" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.Debug" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.EventSource" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.CommandLine" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.FileExtensions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Configuration.Json" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.Console" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 60 - 0
SHJX.Service.Common/packages.config

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="EPPlus" version="5.6.4" targetFramework="net48" />
+  <package id="Expression.Blend.Sdk" version="1.0.2" targetFramework="net48" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration.Abstractions" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration.Binder" version="7.0.3" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration.EnvironmentVariables" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration.FileExtensions" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration.Json" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Configuration.Xml" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.DependencyInjection" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.DependencyModel" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.FileProviders.Abstractions" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.FileProviders.Physical" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.FileSystemGlobbing" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Hosting" version="7.0.1" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Logging" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Logging.Abstractions" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Logging.Console" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Logging.Debug" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Options" version="7.0.1" targetFramework="net48" />
+  <package id="Microsoft.Extensions.Primitives" version="7.0.0" targetFramework="net48" />
+  <package id="Microsoft.Office.Interop.Excel" version="15.0.4795.1001" targetFramework="net48" />
+  <package id="Microsoft.Office.Interop.Word" version="15.0.4797.1004" targetFramework="net48" />
+  <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
+  <package id="NPOI" version="2.5.2" targetFramework="net48" />
+  <package id="Panuon.UI.Core" version="2.2.1" targetFramework="net48" />
+  <package id="Panuon.UI.Silver" version="1.1.3.3" targetFramework="net48" />
+  <package id="Portable.BouncyCastle" version="1.8.10" targetFramework="net48" />
+  <package id="Prism.Core" version="8.0.0.1909" targetFramework="net48" />
+  <package id="Serilog" version="3.0.1" targetFramework="net48" />
+  <package id="Serilog.Enrichers.Thread" version="3.2.0-dev-00752" targetFramework="net48" />
+  <package id="Serilog.Extensions.Logging" version="7.0.0" targetFramework="net48" />
+  <package id="Serilog.Formatting.Compact" version="1.1.1-dev-00944" targetFramework="net48" />
+  <package id="Serilog.Settings.Configuration" version="7.0.0" targetFramework="net48" />
+  <package id="Serilog.Sinks.Console" version="4.1.1-dev-00896" targetFramework="net48" />
+  <package id="Serilog.Sinks.File" version="5.0.1-dev-00947" targetFramework="net48" />
+  <package id="SharpZipLib" version="1.3.1" targetFramework="net48" />
+  <package id="SqlSugar" version="5.0.2.6" targetFramework="net48" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net48" />
+  <package id="System.ComponentModel.Annotations" version="4.7.0" targetFramework="net48" />
+  <package id="System.Diagnostics.DiagnosticSource" version="7.0.1" targetFramework="net48" />
+  <package id="System.IO" version="4.3.0" targetFramework="net48" />
+  <package id="System.Memory" version="4.5.5" targetFramework="net48" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
+  <package id="System.Runtime" version="4.3.0" targetFramework="net48" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
+  <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
+  <package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net48" />
+  <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
+  <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
+  <package id="System.Security.Cryptography.Xml" version="7.0.0" targetFramework="net48" />
+  <package id="System.Text.Encodings.Web" version="7.0.0" targetFramework="net48" />
+  <package id="System.Text.Json" version="7.0.0" targetFramework="net48" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
+</packages>

+ 32 - 0
SHJX.Service.Control/Common/OptionSilverSulfatePipe.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SHJX.Service.Control.Common
+{
+    public class OptionSilverSulfatePipe : IDisposable
+    {
+        private readonly string Position;
+        public OptionSilverSulfatePipe(string position)
+        {
+            Position = position;
+            bool res;
+            do
+            {
+                res = DataCentre.GetStorageContent.Factory("SilverSulfatePipe").Start(Position);
+            } while (!res);
+        }
+
+        public void Dispose()
+        {
+            bool res;
+            do
+            {
+                res = DataCentre.GetStorageContent.Factory("SilverSulfatePipe").Stop(Position);
+            } while (!res);
+            
+        }
+    }
+}

+ 216 - 0
SHJX.Service.Control/ControlProxy/ProxyRouteContext.cs

@@ -0,0 +1,216 @@
+using System;
+using System.Reflection;
+using SHJX.Service.Common.Proxy;
+using System.Collections.Generic;
+using SHJX.Service.Common.ReadXML;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.Interface;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.UserDelegate;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+namespace SHJX.Service.Control.ControlProxy
+{
+    /// <summary>
+    /// 路由代理
+    /// </summary>
+    public class ProxyRouteContext : InterceptorImp
+    {
+        private readonly Dictionary<string, FlowControlOperateImp> _responsibilities = new();
+        private static ReadConfigUtil _config;
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(ProxyRouteContext));
+        /// <summary>
+        /// 无参构造
+        /// </summary>
+        public ProxyRouteContext() { }
+
+        /// <summary>
+        /// 有参构造
+        /// </summary>
+        /// <param name="config"></param>
+        public ProxyRouteContext(ReadConfigUtil config)
+        {
+            var proxies = config.GetResponsibility;
+            _config = config;
+            foreach (var item in proxies)
+            {
+                var operate = SetProxy(Assembly.GetExecutingAssembly().CreateInstance(item.Proxy));
+                _responsibilities.Add(item.Name, operate);
+            }
+        }
+
+        /// <summary>
+        /// 调用代理方法
+        /// </summary>
+        /// <param name="proxyName"></param>
+        /// <param name="data"></param>
+        public void InvokeProxyMethod(string proxyName, DataEventArgs data) =>
+            _responsibilities[proxyName]?.Operate(_config, data);
+
+        private FlowControlOperateImp SetProxy<T>(T type) where T : class =>
+            (FlowControlOperateImp)Proxy.NewProxyInstance(type.GetType(), this);
+
+        /// <summary>
+        /// 代理调用
+        /// </summary>
+        /// <param name="object">被代理对象</param>
+        /// <param name="method">调用方法</param>
+        /// <param name="parameters">参数</param>
+        /// <returns></returns>
+        public object Invoke(object @object, string method, object[] parameters)
+        {
+            var type = @object.GetType();
+            var retObj = type.GetMethod(method)?.Invoke(@object, parameters);
+            
+            return AfterInvoke(method, parameters, retObj);
+        }
+
+        /// <summary>
+        /// 调用之后的处理
+        /// </summary>
+        /// <param name="method"></param>
+        /// <param name="parameters"></param>
+        /// <param name="retObj"></param>
+        /// <returns></returns>
+        private static object AfterInvoke(string method, object[] parameters, object retObj)
+        {
+            if (parameters[1] is not DataEventArgs data) return retObj;
+            try
+            {
+                if (!method.Equals("Operate"))
+                    return retObj;
+                if (!Convert.ToBoolean(retObj))
+                    return retObj;
+                if (Convert.ToBoolean(retObj) == true)
+                {
+                    logger.LogInformation("任务Local:{0} -> RouteID:{1},RouteStepID:{2},RouteStep:{3},下发成功!", data.Task.OriginLocalName, data.Task.RouteId, data.Task.RouteStepId, data.Task.RouteStep);
+                    UpdateStateInEquipment(data);
+                    ChangeMainElement(data);
+                    MakeMotorChangeZero(data);
+                    UpdateRoute(data);
+                    NotExistTask(data);
+                }
+                return retObj;
+            }
+            catch (Exception ex)
+            {
+                logger.LogError(ex.ToString());
+                return retObj;
+            }
+        }
+
+        /// <summary>
+        /// 路由更新
+        /// </summary>
+        /// <param name="data"></param>
+        private static void UpdateRoute(DataEventArgs data)
+        {
+            switch (data.Task.RouteUpdateType)
+            {
+                case "Single":
+                    data.Task.UpdateRouteStep();
+                    break;
+                case "ALL":
+                    data.Task.UpdateStepToTask();
+                    break;
+            }
+        }
+
+       
+
+        /// <summary>
+        /// 改变主界面元素
+        /// </summary>
+        /// <param name="data"></param>
+        private static void ChangeMainElement(DataEventArgs data)
+        {
+            var changeElementArgs = new Dictionary<string, string>
+            {
+                {"localName",data.Task.OriginLocalName.Replace("A",string.Empty)},
+                {"to", data.Task.To},
+                {"from", data.Task.From},
+                {"level", data.Task.SampleConcentration}
+            };
+            (string keyName, string type) = (data.Task.RouteType, data.Task.RouteId, data.Task.RouteStepId, data.Task.TaskType) switch
+            {
+                ("SY", 2, 8, "水样" or "空白" or "补杯" ) or ("BD", 2, 8, "标定") or ("Wash", 1, 8, "润洗") or ("Wash", 1, 8, "清洗") => ("type", "IC"),//进入消解位
+                ("SY", 7, 6, "水样" or "空白" or "补杯" ) or ("BD", 5, 6, "标定") or ("Wash", 4, 6, "润洗") or ("Wash", 4, 6, "清洗") => ("type", "OC"),//离开消解位
+                ("SY", 7, 8, "水样" or "空白" ) or ("BD", 5, 8, "标定") => ("type", "IS"), //进入搅拌位
+                ("SY", 8, 8, "水样" or "空白" ) or ("BD", 6, 8, "标定") => ("type", "OS"), //离开搅拌位
+                ("SY", 10, 8, "水样" or "空白" or "补杯") or ("BD", 8, 8, "标定") or ("Wash", 4, 8, "润洗") or ("Wash", 4, 8, "清洗") => ("type", "IT"), //进入滴定位
+                ("SY", 11, 6, "水样" or "空白" or "补杯") or ("BD", 9, 6, "标定") or ("Wash", 5, 6, "润洗") or ("Wash", 5, 6, "清洗") => ("type", "OT"), //离开滴定位
+                ("SY", 11, 8, "水样" or "空白" or "补杯") or ("BD", 9, 8, "标定") => ("type", "TtS"), //任务结束
+                ("SY", 7, 8, "补杯") => ("type", "TtS"), //任务结束
+                _ => (null, null)
+            };
+            if (keyName is not null ) changeElementArgs.Add(keyName, type);
+            if (changeElementArgs.ContainsKey("type")) Messager.Send("changeElement", changeElementArgs);
+        }
+       
+
+        /// <summary>
+        /// 更新位置状态
+        /// </summary>
+        /// <param name="data"></param>
+        private static void UpdateStateInEquipment(DataEventArgs data)
+        {
+            switch (data.Task.RouteType, data.Task.RouteId, data.Task.RouteStepId)
+            {
+                case ("SY", 7, 8) or ("SY", 8, 8) or ("SY", 11, 8) or ("Wash", 4, 19) or ("Wash", 5, 8) or ("BD", 5, 8) or ("BD", 6, 8) or ("BD", 9, 8):
+                    data.Task.From.UpdateAreaPointState();
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// 不存在任务
+        /// </summary>
+        /// <param name="data"></param>
+        private static void NotExistTask(DataEventArgs data)
+        {
+            switch (data.Task.RouteType, data.Task.RouteId, data.Task.RouteStepId)
+            {
+                case ("SY", 11, 8)  or ("BD", 9, 8) or ("Wash", 5, 8):
+                    if (!data.DataManager.GetTaskIsDoingOrNew())
+                    {
+                        Messager.Send("TaskOver"); //任务结束
+                        if (data.Task.RouteType!="Wash") 
+                        {
+                            Messager.Send("ScreenShot"); //截屏
+                            Messager.Send("ExportResultWord", data.Task.WaveKey); //导出Word
+                        }
+                    }
+                    if (data.Task.RouteType == "BD" && !data.DataManager.GetEquipmentTasksBD(data.Task.WaveKey))//所有标定执行完毕
+                    {
+                        Messager.Send("BD", data.Task.WaveKey); //标定任务结束后补杯
+                    }
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// 将电机点位置为零
+        /// </summary>
+        /// <param name="data"></param>
+        private static void MakeMotorChangeZero(DataEventArgs data)
+        {
+            try
+            {
+                var motorName = data.Task.RouteStepId switch
+                {
+                    6 or 8 => "MotorZ",
+                    10 or 29 => "MotorT",
+                    _ => null
+                };
+                if (!string.IsNullOrWhiteSpace(motorName))
+                {
+                    motorName.UpdateMotorPoint();
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+    }
+}

+ 105 - 0
SHJX.Service.Control/CreateTask.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Linq;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Model.Control;
+
+namespace SHJX.Service.Control
+{
+    public static class CreateTask
+    {
+        /// <summary>
+        /// 创建水样任务
+        /// </summary>
+        /// <returns></returns>
+        public static EquipmentTask NewSampleTask(this SampleDetail args, string waveKey)
+        {
+            (string OperateType, int Priority) = args.SampleType switch
+            {
+                "水样" => ("SY", 200),
+                "补杯" => ("SY", 100),
+                "空白" => ("SY", 300),
+                "标定" => ("BD", 400),
+                _ => throw new ArgumentNullException(args.SampleType)
+            };
+            var initRoute = DataCentre.GetRoutes.FirstOrDefault(item => item.OpKey.Equals(OperateType) && item.RouteId.Equals(1) && item.IsInit.Equals("Y"));
+            var initStep = DataCentre.GetSteps.FirstOrDefault(item => initRoute != null && item.RouteStepId.Equals(initRoute.InitStep));
+            var task = new EquipmentTask
+            {
+                Priority = Priority,
+                WaveKey = waveKey,
+                OriginLocalName = args.NodeName,
+                From = args.NodeName,
+                To = args.NodeName,
+                RouteId = initRoute.RouteId,
+                RouteStepId = initRoute.InitStep,
+                RouteStep = initStep?.RouteStepName,
+                RouteType = initRoute?.OpKey,
+                RouteUpdateType = initStep?.UpdateRange,
+                TaskDetailName = args.Detail,
+                GetSampleVolume = args.SampleVolume,
+                GetSampleMultiple = args.Multiple,
+                TaskType = args.SampleType,
+                SampleConcentration = args.Concentration.Equals("低") ? "Low" : "High",
+                HgSO4Volume = args.HgSO4,
+                QuickTitration = args.QuickTitration
+            };
+            return task;
+        }
+
+        /// <summary>
+        /// 创建Wash任务
+        /// </summary>
+        /// <returns></returns>
+        public static EquipmentTask NewWashTask(this string detailName)
+        {
+            var initRoute = DataCentre.GetRoutes.FirstOrDefault(item => item.OpKey.Equals("Wash") && item.RouteId.Equals(1) && item.IsInit.Equals("Y"));
+            if (initRoute == null)
+                return null;
+            var initStep = DataCentre.GetSteps.FirstOrDefault(item => item.RouteStepId.Equals(initRoute.InitStep));
+            var area = DataCentre.GetAreas;
+            var localName = area.FirstOrDefault(item => item.AreaName.Equals(initRoute.From))?.PointName;
+            var toLocalName = area.FirstOrDefault(item =>
+                item.AreaName.Equals(initRoute.To) &&
+                item.Status.Equals(true))?.PointName;
+            var task = new EquipmentTask
+            {
+                TaskType = detailName,
+                Priority = 500,
+                Status = TaskState.Doing,
+                OriginLocalName = localName,
+                From = localName,
+                To = toLocalName,
+                RouteId = initRoute.RouteId,
+                RouteStepId = initRoute.InitStep,
+                RouteStep = initStep?.RouteStepName,
+                RouteType = initRoute.OpKey,
+                RouteUpdateType = initStep?.UpdateRange
+            };
+            return task;
+        }
+
+        /// <summary>
+        /// 创建单点任务
+        /// </summary>
+        /// <param name="pointName"></param>
+        /// <returns></returns>
+        public static EquipmentTask NewSinglePointTask(this string pointName)
+        {
+            var initRoute = DataCentre.GetRoutes.FirstOrDefault(item => item.OpKey.Equals("SP") && item.RouteId.Equals(1) && item.IsInit.Equals("Y"));
+            var initStep = DataCentre.GetSteps.FirstOrDefault(item => initRoute != null && item.RouteStepId.Equals(initRoute.InitStep));
+            var task = new EquipmentTask
+            {
+                Status = TaskState.Doing,
+                OriginLocalName = pointName,
+                From = pointName,
+                To = pointName,
+                RouteId = 1,
+                RouteStepId = initRoute.InitStep,
+                RouteType = initRoute.OpKey,
+                RouteStep = initStep?.RouteStepName,
+                RouteUpdateType = initStep?.UpdateRange
+            };
+            return task;
+        }
+    }
+}

+ 542 - 0
SHJX.Service.Control/DataCentre.cs

@@ -0,0 +1,542 @@
+using System;
+using System.Linq;
+using SHJX.Service.Dao;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Common.Model;
+using SHJX.Service.ServerClient;
+using System.Collections.Generic;
+using SHJX.Service.Common.Extend;
+using SHJX.Service.Common.Camera;
+using SHJX.Service.Common.ReadXML;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.ControlProxy;
+using SHJX.Service.Control.PortOperate.Content;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+
+namespace SHJX.Service.Control
+{
+    public static class DataCentre
+    {
+        private static CreatGraber _graber;
+        private static CreatGraber _graber2;
+        private static OptClient _optClient;
+        private static ReadConfigUtil _config;
+        private static LiquidDropArgs _dropArgs;
+        private static LiquidDropArgs _dropArgs2;
+        public static OperateDataManager _dataManager;
+        private static ProxyRouteContext _proxyContent;
+        private static TimerController _timerController;
+        private static PumpContent _pumpContent;
+        private static StorageContent _storageContent;
+        private static List<Routes> _routes;
+        private static List<Step> _steps;
+        private static List<RouteStep> _routeSteps;
+        private readonly static object obj_area_lock = new();
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(DataCentre));
+        /// <summary>
+        /// 初始化
+        /// </summary>
+        /// <param name="config">配置</param>
+        /// <param name="log">日志</param>
+        public static void InitBaseData(ReadConfigUtil config,out int cameraNum)
+        {
+            try
+            {
+                _config = config;
+                _dataManager = new(_config);
+                _proxyContent = new(_config);
+                _optClient = new( _config);
+                _graber = new(_config.LogiCamera1, out int ret1);
+                _graber2 = new(_config.LogiCamera2, out int ret2);
+                _dropArgs = new(_graber.WebCam, _graber.Graber);
+                _dropArgs2 = new(_graber2.WebCam, _graber2.Graber);
+
+                cameraNum = ret1 + ret2;
+                if (_config.LogiCamera1 == _config.LogiCamera2 && cameraNum==2)
+                { 
+                    cameraNum = 1;
+                    _graber2 = _graber;
+                    _dropArgs2 = _dropArgs;
+                }
+                _pumpContent = new(_optClient);
+                _storageContent = new(_optClient);
+                InitBasementData(_dataManager);
+                _timerController = new( _config);
+            }
+            catch (Exception ex)
+            {
+                logger.LogError(ex.Message);
+                cameraNum = 0;
+            }
+        }
+
+        /// <summary>
+        /// 初始化基础数据
+        /// </summary>
+        /// <param name="dataManager"></param>
+        private static void InitBasementData(OperateDataManager dataManager)
+        {
+            _routes = dataManager.Query<Routes>().ToList();
+            _routeSteps = dataManager.Query<RouteStep>().ToList();
+            _steps = dataManager.Query<Step>().ToList();
+        }
+        public static int getDigestionPositionCount()
+        {
+            int rtn = _dataManager.Query<EquipmentArea>().Count(item => item.AreaName.Equals("DigestionPosition") && item.Status == true);
+            return rtn;
+        }
+
+        public static ReadConfigUtil GetConfig => _config;
+        public static OptClient GetClient => _optClient;
+        public static TimerController GetTimer => _timerController;
+        public static PumpContent GetPumpContent => _pumpContent;
+        public static StorageContent GetStorageContent => _storageContent;
+        public static List<EquipmentMotor> GetMotor => _dataManager.Query<EquipmentMotor>().ToList();
+        public static List<EquipmentArea> GetAreas => _dataManager.Query<EquipmentArea>().ToList();
+        public static List<EquipmentStatus> GetEquipmentStatuses => _dataManager.Query<EquipmentStatus>().ToList();
+        public static List<HeatingSetting> GetLiquidHeatings => _dataManager.Query<HeatingSetting>().ToList();
+        public static TitrationValue GetBalanceValue => _dataManager.Query<TitrationValue>().First();
+        public static List<RouteStep> GetRouteSteps => _routeSteps;
+        public static List<Routes> GetRoutes => _routes;
+        public static List<Step> GetSteps => _steps;
+        public static WebCamera GetCamera => _graber.WebCam;
+        public static WebCamera GetCamera2 => _graber2.WebCam;
+        public static CoolingSetting GetCoolingSetting(this string value) =>
+            _dataManager.Query<CoolingSetting>().FirstOrDefault(item => item.TypeName.Equals(value));
+
+        /// <summary>
+        /// 根据任务获取下一段路由步骤
+        /// </summary>
+        /// <param name="task"></param>
+        /// <returns></returns>
+        private static int NextStepByTask(this EquipmentTask task)
+        {
+            var first = _routeSteps.FirstOrDefault(
+                item => item.RouteOpKey.Equals(task.RouteType)
+                        && item.RouteId.Equals(task.RouteId)
+                        && item.RouteStepId.Equals(task.RouteStepId));
+            if (first != null)
+                return first.NextStep;
+            throw new Exception();
+        }
+
+        /// <summary>
+        /// 获取加热时间
+        /// </summary>
+        /// <param name="heatStep"></param>
+        /// <returns></returns>
+        public static HeatingSetting GetHeatingTime(this string heatStep) =>
+            GetLiquidHeatings.FirstOrDefault(item => item.CurrentStep.Equals(heatStep));
+
+        /// <summary>
+        /// 获取加热总数
+        /// </summary>
+        /// <returns></returns>
+        public static int GetHeatingTimeSum() => GetLiquidHeatings.Sum(item => item.HeatingTime);
+
+        public static int MotorFinal(this string motorName)
+        {
+            var motor = GetMotor.FirstOrDefault(item => item.EquipmentName.Equals(motorName));
+            return Math.Abs(Convert.ToInt32(Math.Round(motor.FinalPoint * motor.ConvertRatio)));
+        }
+
+        /// <summary>
+        /// 获取移动距离
+        /// </summary>
+        /// <param name="location"></param>
+        /// <param name="pointName"></param>
+        /// <returns></returns>
+        public static int CalculateAreaPoint(this string location, string pointName)
+        {
+            var motor = GetMotor.First(item => item.EquipmentName.Equals(pointName));
+            var area = GetAreas.FirstOrDefault(item => item.PointName.Equals(location));
+            if (motor is null) throw new ArgumentNullException(pointName);
+            var value = 0.0;
+            if (area is null)
+                return 0;
+            value = pointName switch
+            {
+                "MotorX" => area.LocationX - motor.FinalPoint,
+                "MotorY" => area.LocationY - motor.FinalPoint,
+                "MotorZ" => area.LocationZ,
+                "MotorT" => area.LocationT - motor.FinalPoint,
+                _ => throw new ArgumentNullException(pointName)
+            };
+            motor.FinalPoint += value;
+            if (pointName== "MotorY")
+            {
+                return Convert.ToInt32(Math.Round(-value * motor.ConvertRatio));
+            }
+            var row = _dataManager.Update(motor);
+            return Convert.ToInt32(Math.Round(-value * motor.ConvertRatio));
+        }
+
+        /// <summary>
+        /// 获取设备对应状态
+        /// </summary>
+        /// <param name="equipmentName"></param>
+        /// <returns></returns>
+        public static EquipmentStatus GetEquipmentState(this string equipmentName) =>
+            GetEquipmentStatuses.FirstOrDefault(item => item.EquipmentName.Equals(equipmentName));
+
+
+        /// <summary>
+        /// 获取路由步骤名称
+        /// </summary>
+        /// <param name="routeStepId"></param>
+        /// <returns></returns>
+        private static Step GetStepName(this int routeStepId) =>
+            _steps.FirstOrDefault(step => step.RouteStepId.Equals(routeStepId));
+
+        /// <summary>
+        /// 组装下发任务数据参数
+        /// </summary>
+        /// <param name="task"></param>
+        /// <returns></returns>
+        public static DataEventArgs GetDataArgs(this EquipmentTask task)
+        {
+            return new DataEventArgs
+            {
+                Proxy = _proxyContent,
+                DataManager = _dataManager,
+                Client = _optClient,
+                LiquidDrop = _dropArgs,
+                Task = task,
+                Motor = GetMotor,
+            };
+        }
+
+        /// <summary>
+        /// 将点位状态置为可用
+        /// </summary>
+        /// <param name="fromPoint"></param>
+        public static void UpdateAreaPointState(this string fromPoint)
+        {
+            var area = GetAreas.FirstOrDefault(item => item.PointName.Equals(fromPoint));
+            if (area is null) return;
+            area.Status = true;
+            _dataManager.Update(area);
+            logger.LogInformation($"将点位{fromPoint}置为可用");
+        }
+
+        /// <summary>
+        /// 改变设备的最后地址
+        /// </summary>
+        /// <param name="equipmentName"></param>
+        public static void UpdateMotorPoint(this string equipmentName)
+        {
+            var area = GetMotor.FirstOrDefault(item => item.EquipmentName.Equals(equipmentName));
+            if (area is null) return;
+            area.FinalPoint = 0;
+            _dataManager.Update(area);
+        }
+
+        /// <summary>
+        /// 获取设备区域的点位
+        /// </summary>
+        /// <param name="outArea"></param>
+        /// <param name="routeId"></param>
+        public static bool GetEquipmentAreaPoint(this int routeId, out string outArea, string opKey,string HighOrLow)
+        {
+            lock (obj_area_lock)
+            {
+                outArea = null;
+                var route = _routes.FirstOrDefault(item => item.RouteId.Equals(routeId) && item.OpKey.Equals(opKey));
+                if (route != null && route.From.Equals(route.To))
+                    return true;
+                if (route != null && route.To.In("SamplePosition", "LiquidWastePosition"))
+                    return true;
+
+                //EquipmentArea area;
+                //if (route.To == "TitrationPosition" && HighOrLow == "High")
+                //{
+                //    area = GetAreas.Where(item => route != null && item.AreaName.Equals(route.To) && item.PointName.Equals("D1"))
+                //      .FirstOrDefault(item => item.Status.Equals(true));
+                //    if (area == null)
+                //        return false;
+                //    area = GetAreas.Where(item => route != null && item.AreaName.Equals(route.To) && item.PointName.Equals("D2"))
+                //      .FirstOrDefault(item => item.Status.Equals(true));
+                //    if (area == null)
+                //        return false;
+                //    area.Status = false;
+                //    _dataManager.Update(area);
+                //    outArea = "D2";
+                //    return true;
+                //}
+                //else if (route.To == "TitrationPosition" && HighOrLow == "Low")
+                //{
+                //    area = GetAreas.Where(item => route != null && item.AreaName.Equals(route.To) && item.PointName.Equals("D2"))
+                //     .FirstOrDefault(item => item.Status.Equals(true));
+                //    if (area == null)
+                //        return false;
+                //    area = GetAreas.Where(item => route != null && item.AreaName.Equals(route.To) && item.PointName.Equals("D1"))
+                //      .FirstOrDefault(item => item.Status.Equals(true));
+                //    if (area == null)
+                //        return false;
+                //    area.Status = false;
+                //    _dataManager.Update(area);
+                //    outArea = "D1";
+                //    return true;
+                //}
+                //else
+                //{
+                //    area = GetAreas.Where(item => route != null && item.AreaName.Equals(route.To))
+                //       .FirstOrDefault(item => item.Status.Equals(true));
+                //    if (area == null)
+                //        return false;
+                //    area.Status = false;
+                //    _dataManager.Update(area);
+                //    outArea = area.PointName;
+                //    return true;
+                //}
+                #region
+                var area = GetAreas.Where(item => route != null && item.AreaName.Equals(route.To))
+                   .FirstOrDefault(item => item.Status.Equals(true));
+                if (area == null)
+                    return false;
+                area.Status = false;
+                _dataManager.Update(area);
+                outArea = area.PointName;
+                return true;
+                #endregion 
+            }
+        }
+
+        #region 路由更新
+
+        /// <summary>
+        /// 更新路由(集中更新)
+        /// </summary>
+        /// <param name="task"></param>
+        public static void UpdateStepToTask(this EquipmentTask task)
+        {
+            try
+            {
+
+                var tasks = _dataManager.GetIsHaveEquipment(task);
+                //做全部更新 更新到下一段路由
+                //if (task.TaskType== "Clear_Out")
+                //{
+                //    task.RouteId = 12;
+                //    task.RouteStepId = 1;
+                //    tasks = _dataManager.GetIsHaveEquipment(task);
+                //}
+                var firstTask = tasks?.FirstOrDefault();
+                if (firstTask is null) return;
+                var nextstep = firstTask.NextStepByTask();
+                //当stepId相等时,检查路由ID
+                if (firstTask.RouteStepId.Equals(nextstep))
+                {
+                    var nextRoute = GetRoutes.FirstOrDefault(item =>
+                        item.OpKey.Equals(firstTask.RouteType) && item.RouteId.Equals(firstTask.RouteId));
+                   
+                    if (nextRoute is not null)
+                    {
+                        //查看路由Id是否相等,相等则置为完成
+                        if (nextRoute.NextRoute.Equals(nextRoute.RouteId))
+                        {
+                            tasks.ForEach(item => { item.Status = TaskState.Finished; });
+                            _dataManager.Update(tasks);
+                            return;
+                        }
+
+                        var nextRouteInitStep = GetRoutes.FirstOrDefault(item =>
+                            item.RouteId.Equals(nextRoute.NextRoute) && item.OpKey.Equals(firstTask.RouteType));
+                        if (nextRouteInitStep is null) return;
+                        var nextRouteInitStepName = nextRouteInitStep.InitStep.GetStepName();
+                        tasks.ForEach(item =>
+                        {
+                            if (nextRouteInitStep.From != nextRouteInitStep.To)
+                            {
+                                if (nextRouteInitStep.To.In("LiquidWastePosition", "SamplePosition"))
+                                {
+                                    item.From = item.To;
+                                    item.To = item.OriginLocalName;
+                                }
+
+                                if (nextRouteInitStep.From.In("LiquidWastePosition", "SamplePosition"))
+                                {
+                                    item.From = item.OriginLocalName;
+                                }
+                            }
+
+                            if (nextRouteInitStep.From.Equals(nextRouteInitStep.To))
+                                item.From = item.To;
+                            item.Status = TaskState.New;
+                            item.RouteId = nextRoute.NextRoute;
+                            item.RouteStepId = nextRouteInitStep.InitStep;
+                            item.RouteStep = nextRouteInitStepName?.RouteStepName;
+                            item.RouteUpdateType = nextRouteInitStepName?.UpdateRange;
+                        });
+                        var initStep1 = nextstep.GetStepName();
+                        if (initStep1.RouteStepName== "Clear_Cooling")
+                        {
+                            var routeStepBB = GetRouteSteps.FirstOrDefault(item => item.RouteStepId.Equals(29) && item.RouteOpKey.Equals(task.RouteType));
+                            var nextStepBB = GetSteps.FirstOrDefault(item => item.RouteStepId.Equals(routeStepBB.RouteStepId));
+                            tasks.ForEach(item =>
+                            {
+                                if (item.TaskType == "补杯")
+                                {
+                                    item.Status = TaskState.New;
+                                    item.RouteStepId = routeStepBB.RouteStepId;
+                                    item.RouteStep = nextStepBB?.RouteStepName;
+                                    item.RouteUpdateType = nextStepBB?.UpdateRange;
+                                    item.RouteId = 6;
+                                }
+                            });
+                        }
+                       
+                        _dataManager.Update(tasks);
+                        return;
+                    }
+                }
+
+                var initStep = nextstep.GetStepName();
+                
+                tasks.ForEach(item =>
+                {
+                    item.Status = TaskState.New;
+                    item.RouteStepId = nextstep;
+                    item.RouteStep = initStep?.RouteStepName;
+                    item.RouteUpdateType = initStep?.UpdateRange;
+                });
+
+                if (initStep.RouteStepName == "CA_Down")
+                {
+                    var routeStepBB = GetRouteSteps.FirstOrDefault(item => item.RouteStepId.Equals(10) && item.RouteOpKey.Equals(task.RouteType));
+                    var nextStep = GetSteps.FirstOrDefault(item => item.RouteStepId.Equals(routeStepBB.RouteStepId));
+
+                    tasks.ForEach(item =>
+                    {
+                        if (item.TaskType == "补杯")
+                        {
+                            item.Status = TaskState.New;
+                            item.RouteStepId = routeStepBB.RouteStepId;
+                            item.RouteStep = nextStep?.RouteStepName;
+                            item.RouteUpdateType = nextStep?.UpdateRange;
+                            item.RouteId = 4;
+                        }
+                    });
+                }
+                
+                _dataManager.Update(tasks);
+               
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e);
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 路由更新(单步更新)
+        /// </summary>
+        /// <param name="task"></param>
+        public static void UpdateRouteStep(this EquipmentTask task)
+        {
+            try
+            {
+                var routeStep = GetRouteSteps.FirstOrDefault(item =>
+                    item.RouteId.Equals(task.RouteId) && item.RouteStepId.Equals(task.RouteStepId) &&
+                    item.RouteOpKey.Equals(task.RouteType));
+                if (routeStep is null) return;
+
+                if (task.TaskType == "补杯" && task.RouteId == 1 && task.RouteStepId == 1)
+                {
+                    var routeStepBB = GetRouteSteps.FirstOrDefault(item => item.RouteStepId.Equals(1) && item.RouteOpKey.Equals(task.RouteType));
+                    var nextStep = GetSteps.FirstOrDefault(item => item.RouteStepId.Equals(routeStepBB.RouteStepId));
+
+                    task.RouteStepId = routeStepBB.RouteStepId;
+                    task.RouteStep = nextStep?.RouteStepName;
+                    task.RouteUpdateType = nextStep?.UpdateRange;
+                    task.Status = TaskState.New;
+                    task.RouteId = 2;
+
+                    _dataManager.Update(task);
+                    return;
+                }
+                if (task.TaskType == "补杯" && task.RouteId == 7 && task.RouteStepId == 1)
+                {
+                    task.To = task.OriginLocalName;
+                }
+                //如果是RouteStepID与NextStep相等,则查找下一段路由
+                if (routeStep.RouteStepId.Equals(routeStep.NextStep) || task.TaskType == "补杯" && task.RouteId == 7 && task.RouteStepId == 8)
+                {
+                    //查找路由
+                    var route = GetRoutes.FirstOrDefault(item =>item.RouteId.Equals(routeStep.RouteId) && item.OpKey.Equals(task.RouteType));
+                    //如果下一段路由的RouteID与NextRoute相等,则代表没有后续步骤,完成任务
+                    if (route is not null && route.RouteId.Equals(route.NextRoute) || task.TaskType == "补杯" && task.RouteId == 7 && task.RouteStepId == 8)
+                    {
+                       
+                        if (task.TaskType == "补杯" && task.RouteId == 7 && task.RouteStepId == 8)
+                        {
+                            _dataManager.Delete(task);
+                        }
+                        else
+                        {
+                            task.Status = TaskState.Finished;
+                            _dataManager.Update(task);
+                        }
+                    }
+                    else
+                    {
+                        var initRoute = GetRoutes.FirstOrDefault(
+                            item => route is not null && item.OpKey.Equals(task.RouteType) &&
+                                    item.RouteId.Equals(route.NextRoute));
+
+                        if (initRoute is not null && initRoute.From != initRoute.To)
+                        {
+                            if (initRoute.To.In("LiquidWastePosition", "SamplePosition"))
+                            {
+                                task.From = task.To;
+                                task.To = task.OriginLocalName;
+                            }
+                            if (initRoute.To.In( "TitrationPosition2"))
+                            {
+                                task.From = task.To;
+                            }
+
+                            if (initRoute.From.In("LiquidWastePosition", "SamplePosition"))
+                            {
+                                task.From = task.OriginLocalName;
+                            }
+                        }
+                        
+                        if (initRoute is not null && initRoute.From.Equals(initRoute.To))
+                            task.From = task.To;
+                        
+                        task.Status = TaskState.New;
+                        if (initRoute is not null)
+                        {
+                            var initStep = GetSteps.FirstOrDefault(item => item.RouteStepId.Equals(initRoute.InitStep));
+                            task.RouteId = initRoute.RouteId;
+                            task.RouteStepId = initRoute.InitStep;
+                            task.RouteStep = initStep?.RouteStepName;
+                            task.RouteUpdateType = initStep?.UpdateRange;
+                        }
+                       
+                        _dataManager.Update(task);
+                    }
+                }
+                else
+                {
+                   
+                    var nextStep = GetSteps.FirstOrDefault(item => item.RouteStepId.Equals(routeStep.NextStep));
+                    task.RouteStepId = routeStep.NextStep;
+                    task.RouteStep = nextStep?.RouteStepName;
+                    task.RouteUpdateType = nextStep?.UpdateRange;
+
+                    _dataManager.Update(task);
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+                throw;
+            }
+        }
+        #endregion
+    }
+}

+ 58 - 0
SHJX.Service.Control/FlowContent.cs

@@ -0,0 +1,58 @@
+using System;
+using SHJX.Service.Dao;
+using System.Reflection;
+using WorkflowCore.Interface;
+using SHJX.Service.Model.Dao;
+using System.Collections.Generic;
+using SHJX.Service.Common.ReadXML;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+namespace SHJX.Service.Control
+{
+    public class FlowContent
+    {
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(FlowContent));
+        protected ServiceDataManager DataManager;
+        private readonly Dictionary<string, TaskFlowImp> _res;
+        private readonly IWorkflowHost host;
+        public FlowContent(  ReadConfigUtil config)
+        {
+            try
+            {
+               
+                _res = new Dictionary<string, TaskFlowImp>();
+                var proxys = config.GetResponsibility;
+                foreach (var item in proxys)
+                {
+                    if (Assembly.GetExecutingAssembly().CreateInstance(item.Flow) is not TaskFlowImp operate) continue;
+                    operate.ProxyName = item.Name;
+                    _res.Add(item.Name, operate);
+                }
+                _res["CC"].SetNextFlow(_res["Ca"]).SetNextFlow(_res["DAL"])
+                .SetNextFlow(_res["DAW"]).SetNextFlow(_res["DM"]).SetNextFlow(_res["DC"])
+                .SetNextFlow(_res["LH"]).SetNextFlow(_res["LS"]).SetNextFlow(_res["Mani"])
+                .SetNextFlow(_res["MTM"]).SetNextFlow(_res["MXM"]).SetNextFlow(_res["MYM"])
+                .SetNextFlow(_res["MZM"]).SetNextFlow(_res["SAL1"]).SetNextFlow(_res["SAL2"])
+                .SetNextFlow(_res["TAL1"]).SetNextFlow(_res["TAL2"]).SetNextFlow(_res["SC"]);
+            }
+            catch (Exception ex)
+            {
+                logger.LogError(ex.ToString());
+            }
+        }
+
+        public void OperateControllerContent(EquipmentTask task)
+        {
+            try
+            {
+                var data = task.GetDataArgs(); 
+                _res["CC"].OperateHandle(data);
+            }
+            catch (Exception ex)
+            {
+                logger.LogError(ex.ToString());
+            }
+        }
+    }
+}

+ 25 - 0
SHJX.Service.Control/Interface/ChangeTaskStateImp.cs

@@ -0,0 +1,25 @@
+using SHJX.Service.Dao;
+using SHJX.Service.Common.ReadXML;
+
+namespace SHJX.Service.Control.Interface
+{
+    /// <summary>
+    /// 改变任务状态
+    /// </summary>
+    public abstract class ChangeTaskStateImp
+    {
+        protected readonly ServiceDataManager DataManager;
+        protected readonly ReadConfigUtil ReadConfig;
+
+        protected ChangeTaskStateImp(ReadConfigUtil config)
+        {
+            ReadConfig ??= config;
+            DataManager ??= new ServiceDataManager(config);
+        }
+
+        /// <summary>
+        /// 改变任务状态
+        /// </summary>
+        public abstract void ChangeEquipmentTaskStateEvent();
+    }
+}

+ 28 - 0
SHJX.Service.Control/Interface/EquipmentFlowImp.cs

@@ -0,0 +1,28 @@
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Interface
+{
+    /// <summary>
+    /// 任务流程
+    /// </summary>
+    public abstract class TaskFlowImp
+    {
+        /// <summary>
+        /// 赋值下一个操作类
+        /// </summary>
+        /// <returns></returns>
+        public TaskFlowImp NextFlow;
+        public string ProxyName { get; set; }
+        public TaskFlowImp SetNextFlow(TaskFlowImp flow)
+        {
+            NextFlow = flow;
+            return NextFlow;
+        }
+
+        /// <summary>
+        /// 具体操作
+        /// </summary>
+        /// <param name="data"></param>
+        public abstract void OperateHandle(DataEventArgs data);
+    }
+}

+ 16 - 0
SHJX.Service.Control/Interface/FlowControlOperateImp.cs

@@ -0,0 +1,16 @@
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Interface
+{
+    /// <summary>
+    /// 这个接口实际只是为了做动态代理
+    /// </summary>
+    public interface FlowControlOperateImp
+    {
+        /// <summary>
+        /// 具体操作
+        /// </summary>
+        /// <param name="data"></param>
+        bool Operate(ReadConfigUtil config,DataEventArgs data);
+    }
+}

+ 27 - 0
SHJX.Service.Control/Interface/WriteTaskToEquipmentImp.cs

@@ -0,0 +1,27 @@
+using SHJX.Service.Dao;
+using SHJX.Service.Common.ReadXML;
+
+namespace SHJX.Service.Control.Interface
+{
+    /// <summary>
+    /// 写入任务到设备
+    /// </summary>
+    public abstract class WriteTaskToEquipmentImp
+    {
+        protected readonly ServiceDataManager DataManager;
+        protected readonly FlowContent OperateController;
+        protected readonly ReadConfigUtil ReadConfig;
+
+        protected WriteTaskToEquipmentImp(ReadConfigUtil config, FlowContent operateController)
+        {
+            ReadConfig ??= config;
+            DataManager ??= new ServiceDataManager(config);
+            this.OperateController ??= operateController;
+        }
+
+        /// <summary>
+        /// 写入任务到设备仪器
+        /// </summary>
+        public abstract void WriteTaskToEquipmentEvent();
+    }
+}

+ 43 - 0
SHJX.Service.Control/Modules/DataEventArgs.cs

@@ -0,0 +1,43 @@
+
+using System;
+using SHJX.Service.Dao;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Common.Model;
+using SHJX.Service.ServerClient;
+using System.Collections.Generic;
+using SHJX.Service.Control.ControlProxy;
+
+namespace SHJX.Service.Control.Modules
+{
+    public class DataEventArgs : EventArgs
+    {
+        /// <summary>
+        /// 代理
+        /// </summary>
+        public ProxyRouteContext Proxy { get; set; }
+        /// <summary>
+        /// 任务
+        /// </summary>
+        public EquipmentTask Task { get; set; }
+        /// <summary>
+        /// 数据库
+        /// </summary>
+        public OperateDataManager DataManager { get; set; }
+        /// <summary>
+        /// 通信
+        /// </summary>
+        public OptClient Client { get; set; }
+        /// <summary>
+        /// 液体加液的参数
+        /// </summary>
+        public LiquidDropArgs LiquidDrop { get; set; }
+        /// <summary>
+        /// 液体加液的参数
+        /// </summary>
+        public LiquidDropArgs LiquidDrop2 { get; set; }
+        /// <summary>
+        /// 电机
+        /// </summary>
+        public List<EquipmentMotor> Motor { get; set; }
+    }
+}

+ 31 - 0
SHJX.Service.Control/PortOperate/Content/PumpContent.cs

@@ -0,0 +1,31 @@
+using System;
+using SHJX.Service.ServerClient;
+using System.Collections.Concurrent;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate.Content
+{
+    public class PumpContent
+    {
+        private readonly ConcurrentDictionary<string, PortOperateImp> _pumpContents;
+
+        public PumpContent(OptClient client)
+        {
+            _pumpContents = new ConcurrentDictionary<string, PortOperateImp>();
+            _pumpContents.TryAdd("PotassiumDichromate_Low", new PotassiumDichromateLowOperatep(client));
+            _pumpContents.TryAdd("PotassiumDichromate_High", new PotassiumDichromateHighOperate(client));
+            _pumpContents.TryAdd("FAS_Low", new FasLowOperate(client));
+            _pumpContents.TryAdd("FAS_High", new FasHighOperate(client));
+            _pumpContents.TryAdd("SilverSulfate", new SilverSulfateOperate(client));
+        }
+
+        public PortOperateImp Factory(string pumpName)
+        {
+            if (_pumpContents.TryGetValue(pumpName, out var pumpOperate))
+            {
+                return pumpOperate;
+            }
+            throw new ArgumentNullException(pumpName);
+        }
+    }
+}

+ 39 - 0
SHJX.Service.Control/PortOperate/Content/StorageContent.cs

@@ -0,0 +1,39 @@
+using System;
+using SHJX.Service.ServerClient;
+using System.Collections.Concurrent;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate.Content
+{
+    public class StorageContent
+    {
+        private readonly ConcurrentDictionary<string, StorageOperateImp> _storages;
+
+        public StorageContent(OptClient client)
+        {
+            _storages = new ConcurrentDictionary<string, StorageOperateImp>();
+            _storages.TryAdd("Timer", new TimerOperate(client));
+            _storages.TryAdd("DripNozzle", new DripNozzleOperate(client));
+            //_storages.TryAdd("DripNozzle2", new DripNozzleOperateHigh(client));
+            //_storages.TryAdd("TitrationStir2", new TitrationStirOperateHigh(client));
+            //_storages.TryAdd("Indicator2", new IndicatorOperateHigh(client));
+            _storages.TryAdd("SilverSulfatePipe", new SilverSulfatePipeOperate(client));
+            _storages.TryAdd("Mercury", new MercuryOperate(client));
+            _storages.TryAdd("Water", new WaterOperate(client));
+            _storages.TryAdd("Indicator", new IndicatorOperate(client));
+            _storages.TryAdd("SampleFan", new SampleFanOperate(client));
+            _storages.TryAdd("DissolveFan", new DissolveFanOperate(client));
+            _storages.TryAdd("LiquidStir", new LiquidStirOperate(client));
+            _storages.TryAdd("TitrationStir", new TitrationStirOperate(client));
+        }
+
+        public StorageOperateImp Factory(string pumpName)
+        {
+            if (_storages.TryGetValue(pumpName, out var storage))
+            {
+                return storage;
+            }
+            throw new ArgumentNullException(pumpName);
+        }
+    }
+}

+ 17 - 0
SHJX.Service.Control/PortOperate/DissolveFanOperate.cs

@@ -0,0 +1,17 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class DissolveFanOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 消解位风扇
+        /// </summary>
+        /// <param name="client"></param>
+        public DissolveFanOperate(OptClient client) : base(client)
+        {
+            OpName = "DissolveFan";
+        }
+    }
+}

+ 38 - 0
SHJX.Service.Control/PortOperate/DripNozzleOperate.cs

@@ -0,0 +1,38 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class DripNozzleOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 滴嘴
+        /// </summary>
+        /// <param name="client"></param>
+        public DripNozzleOperate(OptClient client) : base(client)
+        {
+            OpName = "DripNozzle";
+        }
+
+        public override bool Start(object reserve = null)
+        {
+            var openArgs = new PortArgs
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Normotopia
+            };
+            return Client.Factory(OpName).Write(openArgs);
+        }
+
+        public override bool Stop(object reserve = null)
+        {
+            var openArgs = new PortArgs
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Antiposition
+            };
+            return Client.Factory(OpName).Write(openArgs);
+        }
+    }
+}

+ 38 - 0
SHJX.Service.Control/PortOperate/DripNozzleOperateHigh.cs

@@ -0,0 +1,38 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class DripNozzleOperateHigh : StorageOperateImp
+    {
+        /// <summary>
+        /// 滴嘴
+        /// </summary>
+        /// <param name="client"></param>
+        public DripNozzleOperateHigh(OptClient client) : base(client)
+        {
+            OpName = "DripNozzle2";
+        }
+
+        public override bool Start(object reserve = null)
+        {
+            var openArgs = new PortArgs
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Normotopia
+            };
+            return Client.Factory(OpName).Write(openArgs);
+        }
+
+        public override bool Stop(object reserve = null)
+        {
+            var openArgs = new PortArgs
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Antiposition
+            };
+            return Client.Factory(OpName).Write(openArgs);
+        }
+    }
+}

+ 26 - 0
SHJX.Service.Control/PortOperate/FasHighOperate.cs

@@ -0,0 +1,26 @@
+using System.Threading;
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class FasHighOperate : PortOperateImp
+    {
+        public FasHighOperate(OptClient optClient) : base(optClient)
+        {
+            OpName = "FAS";
+        }
+        public override bool LiquidOperation(string pipeName, long quantity)
+        {
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Antiposition
+            };
+            _client.Factory(OpName).Write(switchArg);
+            Thread.Sleep(1000);
+            return MotorWrite(quantity); //吸液
+        }
+    }
+}

+ 32 - 0
SHJX.Service.Control/PortOperate/FasLowOperate.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using SHJX.Service.Model.Control;
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class FasLowOperate : PortOperateImp
+    {
+        /// <summary>
+        /// 硫酸亚铁铵
+        /// </summary>
+        /// <param name="client"></param>
+        public FasLowOperate(OptClient client) : base(client)
+        {
+            OpName = "FAS";
+        }
+
+        public override bool LiquidOperation(string pipeName, long quantity)
+        {
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Normotopia
+            };
+            _client.Factory(OpName).Write(switchArg);
+            Thread.Sleep(1000);
+            return MotorWrite(quantity); //吸液
+        }
+    }
+}

+ 17 - 0
SHJX.Service.Control/PortOperate/IndicatorOperate.cs

@@ -0,0 +1,17 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class IndicatorOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 指示剂
+        /// </summary>
+        /// <param name="client"></param>
+        public IndicatorOperate(OptClient client) : base(client)
+        {
+            OpName = "Indicator";
+        }
+    }
+}

+ 17 - 0
SHJX.Service.Control/PortOperate/IndicatorOperateHigh.cs

@@ -0,0 +1,17 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class IndicatorOperateHigh : StorageOperateImp
+    {
+        /// <summary>
+        /// 指示剂
+        /// </summary>
+        /// <param name="client"></param>
+        public IndicatorOperateHigh(OptClient client) : base(client)
+        {
+            OpName = "Indicator2";
+        }
+    }
+}

+ 187 - 0
SHJX.Service.Control/PortOperate/Interface/PortOperateImp.cs

@@ -0,0 +1,187 @@
+using Polly;
+using System;
+using System.Threading;
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+
+namespace SHJX.Service.Control.PortOperate.Interface
+{
+    /// <summary>
+    /// 泵操作
+    /// </summary>
+    public abstract class PortOperateImp
+    {
+        protected readonly OptClient _client;
+
+        protected string OpName { get; set; }
+
+        public PortOperateImp(OptClient optClient)
+        {
+            _client = optClient;
+        }
+
+        /// <summary>
+        /// 操作液体的方式
+        /// </summary>
+        /// <param name="quantity">体积</param>
+        /// <param name="pipeName"></param>
+        public virtual bool LiquidOperation(string pipeName, long quantity)
+        {
+            CutPipe(pipeName);
+            return MotorWrite(quantity); //吸液
+        }
+
+        /// <summary>
+        /// 返回原点
+        /// </summary>
+        /// <param name="pipeName">管道名称</param>
+        public virtual void GoBackOriginalPosition(string pipeName)
+        {
+            CutPipe(pipeName);
+            GoBack();
+        }
+
+        /// <summary>
+        /// 返回原点
+        /// </summary>
+        private void GoBack()
+        {
+            PortArgs gobackArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.GoBack
+            };
+            bool gb_write;
+            do
+            {
+                gb_write = _client.Factory(OpName).Write(gobackArg);
+                Thread.Sleep(2000);
+            } while (!gb_write);
+            bool res;
+            do
+            {
+                char[] readRes = Read();
+                if (readRes is null or { Length: 0 })
+                {
+                    Thread.Sleep(1000);
+                    res = false;
+                    continue;
+                };
+                res = readRes[2].Equals('0');
+            } while (!res);
+        }
+
+        /// <summary>
+        /// 到达限位
+        /// </summary>
+        /// <returns></returns>
+        public bool ArriveEndPoint()
+        {
+            var readRes = Read();
+            if (readRes is null or { Length: 0 })
+                return false;
+            return readRes[2].Equals('0');
+        }
+
+        /// <summary>
+        /// 读取寄存器
+        /// </summary>
+        /// <returns></returns>
+        protected char[] Read()
+        {
+            PortArgs readArgs = new()
+            {
+                TypeName = OpName
+            };
+            var readRes = _client.Factory(OpName).Read(readArgs) as char[];
+            return readRes;
+        }
+
+        /// <summary>
+        /// 切换管道
+        /// </summary>
+        /// <param name="pipeName"></param>
+        protected virtual void CutPipe(string pipeName)
+        {
+            var readRes = Read();
+            if (readRes is null or { Length: 0 }) return;
+            OptPipe(() =>
+            {
+                var readRes = Read();
+                if (readRes is null or { Length: 0 })
+                {
+                    Thread.Sleep(1000);
+                    return false;
+                }
+                var res = pipeName switch
+                {
+                    "In" => readRes[5].Equals('0'),
+                    "Out" => readRes[6].Equals('0'),
+                    _ => false,
+                };
+                return res;
+            });
+        }
+
+        /// <summary>
+        /// 选择管道(出 or 吸)
+        /// </summary>
+        /// <param name="func"></param>
+        protected void OptPipe(Func<bool> func)
+        {
+            var res = func.Invoke();
+            if (res) return;
+
+            #region 打开寄存器
+
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Normotopia
+            };
+            bool openRes;
+            do
+            {
+                openRes = _client.Factory(OpName).Write(switchArg);
+                Thread.Sleep(1000);
+            } while (!openRes);
+            #endregion
+
+            Policy.HandleResult<bool>(arg => arg.Equals(false))
+                .RetryForever(_ => { MotorWrite(-100); })
+                .Execute(func);
+
+            #region 关闭该寄存器
+
+            switchArg.WriteWay = WriteWay.Antiposition;
+            do
+            {
+                openRes = _client.Factory(OpName).Write(switchArg);
+                Thread.Sleep(1000);
+            } while (!openRes);
+            #endregion
+        }
+
+        /// <summary>
+        /// 写入脉冲
+        /// </summary>
+        /// <param name="distance"></param>
+        protected bool MotorWrite(long distance)
+        {
+            PortArgs args = new()
+            {
+                TypeName = OpName,
+                WriteWay = WriteWay.Move,
+                Distance = distance
+            };
+            var res = _client.Factory(OpName).Write(args);
+            int speedWaitTime = OpName switch
+            {
+                "SilverSulfate" => 300,
+                _ => 500,
+            };
+            Thread.Sleep((int)(Math.Abs(distance) / 20000 * speedWaitTime));
+            return res;
+        }
+    }
+}

+ 44 - 0
SHJX.Service.Control/PortOperate/Interface/StorageOperateImp.cs

@@ -0,0 +1,44 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+
+namespace SHJX.Service.Control.PortOperate.Interface
+{
+    public abstract class StorageOperateImp
+    {
+        protected string OpName { get; set; }
+        protected readonly OptClient Client;
+
+        protected StorageOperateImp(OptClient client)
+        {
+            Client = client;
+        }
+
+        /// <summary>
+        /// 启动
+        /// </summary>
+        public virtual bool Start(object reserve = null)
+        {
+            PortArgs Args = new()
+            {
+                WriteWay = WriteWay.Start,
+                TypeName = OpName,
+                Reserve= reserve
+            };
+            return Client.Factory(OpName).Write(Args);
+        }
+
+        /// <summary>
+        /// 停止
+        /// </summary>
+        public virtual bool Stop(object reserve = null)
+        {
+            PortArgs Args = new()
+            {
+                WriteWay = WriteWay.Stop,
+                TypeName = OpName,
+                Reserve = reserve
+            };
+            return Client.Factory(OpName).Write(Args);
+        }
+    }
+}

+ 20 - 0
SHJX.Service.Control/PortOperate/LiquidStirOperate.cs

@@ -0,0 +1,20 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    /// <summary>
+    /// 液体搅拌
+    /// </summary>
+    public class LiquidStirOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 液体搅拌
+        /// </summary>
+        /// <param name="client"></param>
+        public LiquidStirOperate(OptClient client) : base(client)
+        {
+            OpName = "LiquidStir";
+        }
+    }
+}

+ 20 - 0
SHJX.Service.Control/PortOperate/MercuryOperate.cs

@@ -0,0 +1,20 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    /// <summary>
+    /// 硫酸汞
+    /// </summary>
+    public class MercuryOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 硫酸汞
+        /// </summary>
+        /// <param name="client"></param>
+        public MercuryOperate(OptClient client) : base(client)
+        {
+            OpName = "Mercury";
+        }
+    }
+}

+ 16 - 0
SHJX.Service.Control/PortOperate/PotassiumDichromateHighOperate.cs

@@ -0,0 +1,16 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class PotassiumDichromateHighOperate : PortOperateImp
+    {
+        /// <summary>
+        /// 高浓度重铬酸钾
+        /// </summary>
+        public PotassiumDichromateHighOperate(OptClient optClient) : base(optClient)
+        {
+            OpName = "PotassiumDichromate_High";
+        }
+    }
+}

+ 16 - 0
SHJX.Service.Control/PortOperate/PotassiumDichromateLowOperatep.cs

@@ -0,0 +1,16 @@
+using SHJX.Service.Control.PortOperate.Interface;
+using SHJX.Service.ServerClient;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    class PotassiumDichromateLowOperatep : PortOperateImp
+    {
+        /// <summary>
+        /// 低浓度重铬酸钾
+        /// </summary>
+        public PotassiumDichromateLowOperatep(OptClient optClient) : base(optClient)
+        {
+            OpName = "PotassiumDichromate_Low";
+        }
+    }
+}

+ 17 - 0
SHJX.Service.Control/PortOperate/SampleFanOperate.cs

@@ -0,0 +1,17 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class SampleFanOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 样品位风扇
+        /// </summary>
+        /// <param name="client"></param>
+        public SampleFanOperate(OptClient client) : base(client)
+        {
+            OpName = "SampleFan";
+        }
+    }
+}

+ 43 - 0
SHJX.Service.Control/PortOperate/SilverSulfateOperate.cs

@@ -0,0 +1,43 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+using SHJX.Service.Control.Common;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class SilverSulfateOperate : PortOperateImp
+    {
+        /// <summary>
+        /// 硫酸银
+        /// </summary>
+        /// <param name="optClient"></param>
+        public SilverSulfateOperate(OptClient optClient) : base(optClient)
+        {
+            OpName = "SilverSulfate";
+        }
+
+        /*protected override void CutPipe(string pipeName)
+        {
+            OptPipe(() =>
+            {
+                var readRes = Read();
+                if (readRes is null || readRes.Length < 1) return false;
+                var res = pipeName switch
+                {
+                    "In" => readRes[6].Equals('0'),
+                    "Out1" => readRes[5].Equals('0'),
+                    "Out2" => readRes[7].Equals('0'),
+                    _ => false,
+                };
+                return res;
+            });
+        }*/
+
+        public override bool LiquidOperation(string pipeName, long quantity)
+        {
+            using (OptionSilverSulfatePipe optPipe = new(pipeName))
+            {
+                return MotorWrite(quantity); //吸液
+            }
+        }
+    }
+}

+ 45 - 0
SHJX.Service.Control/PortOperate/SilverSulfatePipeOperate.cs

@@ -0,0 +1,45 @@
+using SHJX.Service.Control.PortOperate.Interface;
+using SHJX.Service.Model.Control;
+using SHJX.Service.ServerClient;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class SilverSulfatePipeOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 硫酸银管道
+        /// </summary>
+        /// <param name="client"></param>
+        public SilverSulfatePipeOperate(OptClient client) : base(client)
+        {
+            OpName = "SilverSulfatePipe";
+        }
+
+        public override bool Start(object reserve = null)
+        {
+            var openArgs = new PortArgs
+            {
+                TypeName = "SilverSulfate",
+                WriteWay = WriteWay.Start,
+                Reserve = reserve
+            };
+            return Client.Factory(OpName).Write(openArgs);
+        }
+
+        public override bool Stop(object reserve = null)
+        {
+            var openArgs = new PortArgs
+            {
+                TypeName = "SilverSulfate",
+                WriteWay = WriteWay.Stop,
+                Reserve = reserve
+            };
+            return Client.Factory(OpName).Write(openArgs);
+        }
+    }
+}

+ 16 - 0
SHJX.Service.Control/PortOperate/TimerOperate.cs

@@ -0,0 +1,16 @@
+using SHJX.Service.Control.PortOperate.Interface;
+using SHJX.Service.ServerClient;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class TimerOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 计时器
+        /// </summary>
+        public TimerOperate(OptClient client) : base(client)
+        {
+            OpName = "Timer";
+        }
+    }
+}

+ 13 - 0
SHJX.Service.Control/PortOperate/TitrationStirOperate.cs

@@ -0,0 +1,13 @@
+using SHJX.Service.Control.PortOperate.Interface;
+using SHJX.Service.ServerClient;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class TitrationStirOperate : StorageOperateImp
+    {
+        public TitrationStirOperate(OptClient client) : base(client)
+        {
+            OpName = "TitrationStir";
+        }
+    }
+}

+ 13 - 0
SHJX.Service.Control/PortOperate/TitrationStirOperateHigh.cs

@@ -0,0 +1,13 @@
+using SHJX.Service.Control.PortOperate.Interface;
+using SHJX.Service.ServerClient;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class TitrationStirOperateHigh : StorageOperateImp
+    {
+        public TitrationStirOperateHigh(OptClient client) : base(client)
+        {
+            OpName = "TitrationStir2";
+        }
+    }
+}

+ 16 - 0
SHJX.Service.Control/PortOperate/WaterOperate.cs

@@ -0,0 +1,16 @@
+using SHJX.Service.ServerClient;
+using SHJX.Service.Control.PortOperate.Interface;
+
+namespace SHJX.Service.Control.PortOperate
+{
+    public class WaterOperate : StorageOperateImp
+    {
+        /// <summary>
+        /// 水
+        /// </summary>
+        public WaterOperate(OptClient client) : base(client)
+        {
+            OpName = "Water";
+        }
+    }
+}

+ 36 - 0
SHJX.Service.Control/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("SHJX.Service.Control")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SHJX.Service.Control")]
+[assembly: AssemblyCopyright("Copyright ©  2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("1520f6ba-e2b2-4dc5-8552-be7f4c86bc30")]
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
+//通过使用 "*",如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 57 - 0
SHJX.Service.Control/Route/RouteController/CageOperate.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Threading;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 冷凝管
+    /// </summary>
+    public class CageOperate : FlowControlOperateImp
+    {
+        private const string OpName = "Cage";
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            var cage = OpName.GetEquipmentState();
+            var res = false;
+            switch (data.Task.RouteStep)
+            {
+                case "CA_Down":
+                    if (cage.EquipmentState.Equals(EquipmentEnum.Normotopia))
+                    {
+                        var way = WriteWay.Antiposition;
+                        cage.EquipmentState = EquipmentEnum.Antiposition;
+                        var arg = CageWriteOperate(way);
+                        res = data.Client.Factory(OpName).Write(arg);
+                    }
+                    break;
+                case "CA_Rise":
+                    if (cage.EquipmentState.Equals(EquipmentEnum.Antiposition))
+                    {
+                        var way = WriteWay.Normotopia;
+                        cage.EquipmentState = EquipmentEnum.Normotopia;
+                        var arg = CageWriteOperate(way);
+                        res = data.Client.Factory(OpName).Write(arg);
+                        Thread.Sleep(Convert.ToInt32(DataCentre.GetConfig.CoolingPipeWatingTime * 1000)); //提升起来后需要等待时间
+                    }
+                    break;
+            }
+
+            data.DataManager.Update(cage);
+            return res;
+        }
+
+        private PortArgs CageWriteOperate(WriteWay way)
+        {
+            PortArgs portArgs = new()
+            {
+                TypeName = OpName,
+                WriteWay = way
+            };
+            return portArgs;
+        }
+    }
+}

+ 19 - 0
SHJX.Service.Control/Route/RouteController/CalibrationCoolingOperate.cs

@@ -0,0 +1,19 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 标定冷却
+    /// </summary>
+    public class CalibrationCoolingOperate : FlowControlOperateImp
+    {
+        private const string EquipmentName = "CalibrationCooling";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            return EquipmentName.CoolingTimeCalculate(data.DataManager);
+        }
+
+
+    }
+}

+ 45 - 0
SHJX.Service.Control/Route/RouteController/CoolingCommon.cs

@@ -0,0 +1,45 @@
+using System;
+using SHJX.Service.Dao;
+
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 冷却
+    /// </summary>
+    public static class CoolingCommon
+    {
+        public static bool CoolingTimeCalculate(this string equipmentName, OperateDataManager dataManager)
+        {
+            var status = equipmentName.GetCoolingSetting(); //获取样品位冷却的状态
+            if (status.CoolingState.Equals("Start"))
+            {
+                //计算 当前时间与下发时间的差值
+                var timeSpan = DateTime.Now - status.LastTryTime;
+                var minutes = timeSpan.Hours * 60 + timeSpan.Minutes + timeSpan.Seconds / 60.0;
+                if (minutes < status.CoolingTime) return false;
+                DataCentre.GetStorageContent.Factory(GetOpName(equipmentName)).Stop(); //关闭风扇
+                status.CoolingState = "Stop";
+                dataManager.Update(status);
+                return true;
+            }
+            DataCentre.GetStorageContent.Factory(GetOpName(equipmentName)).Start();//开启风扇
+
+            status.CoolingState = "Start";
+            status.LastTryTime = DateTime.Now;
+            dataManager.Update(status);
+
+            return false;
+        }
+
+        public static string GetOpName(string Name)
+        {
+            string opName = Name switch
+            {
+                "CalibrationCooling" or "SampleCooling" => "SampleFan",
+                "DissolveCooling" => "DissolveFan",
+                _ => throw new ArgumentNullException(Name),
+            };
+            return opName;
+        }
+    }
+}

+ 68 - 0
SHJX.Service.Control/Route/RouteController/DissolveAddLiquidOperate.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Threading;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using System.Linq;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 硫酸银
+    /// </summary>
+    /// 硫酸银
+    public class DissolveAddLiquidOperate : FlowControlOperateImp
+    {
+        private const string OpName = "SilverSulfate";
+        private DropLiquid liquid;
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            liquid = data.DataManager.QueryLiquid(OpName);
+            if (!liquid.Enable) return true;
+
+#if false
+            DataCentre.GetPumpContent.Factory(OpName).GoBackOriginalPosition("In");
+            var res = true;
+            for (int i = 0; i < 2; i++)
+            {
+                var arriveEndPoint = DataCentre.GetPumpContent.Factory(OpName).ArriveEndPoint();
+                if (arriveEndPoint) DataCentre.GetPumpContent.Factory(OpName).LiquidOperation("In", Convert.ToInt32(liquid.SampleVolume));
+                var position = data.Task.To switch
+                {
+                    "R1" or "R2" or "R3" or "R4" or "R5" or "R6" or "R7" or "R8" or "R9" or "R10" or "R11" or "R12" => "Out1",
+                    "R13" or "R14" or "R15" or "R16" or "R17" or "R18" or "R19" or "R20" or "R21" or "R22" or "R23" or "R24" => "Out2",
+                    _ => throw new ArgumentNullException(data.Task.To),
+                };
+                res = res && DataCentre.GetPumpContent.Factory(OpName).LiquidOperation(position, -1 * Convert.ToInt32(liquid.SampleVolume));
+            }
+#endif
+
+            var volume = data.Task.TaskType switch
+            {
+                "润洗" => liquid.WashVolume,
+                "清洗" => liquid.ClearVolume,
+                _ => liquid.SampleVolume,
+            };
+#if false
+            var position = data.Task.To switch
+            {
+                "R1" or "R2" or "R3" or "R4" or "R5" or "R6" or "R7" or "R8" or "R9" or "R10" or "R11" or "R12" => "Out1",
+                "R13" or "R14" or "R15" or "R16" or "R17" or "R18" or "R19" or "R20" or "R21" or "R22" or "R23" or "R24" => "Out2",
+                _ => throw new ArgumentNullException(data.Task.To),
+            };
+#endif
+            var position = data.Task.To switch
+            {
+                "R1" or "R2" or "R3" or "R4" or "R5" or "R6" or "R7" or "R8" or "R9" or "R10" or "R11" or "R12" => "2",
+                "R13" or "R14" or "R15" or "R16" or "R17" or "R18" or "R19" or "R20" or "R21" or "R22" or "R23" or "R24" => "3",
+                _ => throw new ArgumentNullException(data.Task.To),
+            };
+            var res = DataCentre.GetPumpContent.Factory(OpName).LiquidOperation(position, -1 * Convert.ToInt32(volume) /*Convert.ToInt32(volume / DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("SilverSulfate")).LowValue * 20000)*/);
+            Thread.Sleep(1000);
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid1.Total -= 15; //硫酸银 减 15毫升
+            res = res & DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            return res;
+        }
+    }
+}

+ 115 - 0
SHJX.Service.Control/Route/RouteController/DissolveAddWaterOperate.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Threading;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+using System.Collections.Generic;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 加水
+    /// </summary>
+    public class DissolveAddWaterOperate : FlowControlOperateImp
+    {
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(DissolveAddWaterOperate));
+        private const string OpName = "Water";
+        private string localtion;
+        private OptClient _client;
+        private DropLiquid _liquid;
+        private EquipmentTask _task;
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _task = data.Task;
+            _client = data.Client;
+            _liquid = data.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable) return true;
+            localtion = _task.To;
+            logger.LogInformation($"加水{localtion}");
+            DataCentre.GetStorageContent.Factory("Timer").Start();
+            Thread.Sleep(2000);
+            DataCentre.GetStorageContent.Factory("Timer").Stop();
+            var res = _task.RouteType switch
+            {
+                "SY" => SYStep(),
+                "BD" => BDStep(),
+                "Wash" => WashStep(),
+                _ => throw new ArgumentNullException(_task.RouteType),
+            };
+            return res;
+        }
+
+        /// <summary>
+        /// 润洗 && 清洗
+        /// </summary>
+        /// <returns></returns>
+        private bool WashStep()
+        {
+            var value = _task.TaskType switch
+            {
+                "润洗" => _liquid.WashVolume,
+                "清洗" => _liquid.ClearVolume,
+                _ => throw new ArgumentNullException(_task.TaskType),
+            };
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start(localtion);
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * value)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop(localtion);
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid1.Total -= 45; //水减去 45毫升
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            return res;
+        }
+
+        /// <summary>
+        /// 水样
+        /// </summary>
+        /// <returns></returns>
+        /// 水样任务需要分两次加液
+        private bool SYStep()
+        {
+            PortArgs tempPortArgs = new() { TypeName = "Heat" };
+            var tempRes = _client.Factory("Heat").Read(tempPortArgs) as Dictionary<string, double>;
+            if (DataCentre.GetConfig.AddWaterTemperature < tempRes["pv"])
+            {
+                logger.LogInformation($"加水时温度不匹配:加水温度为:{DataCentre.GetConfig.AddWaterTemperature},实际温度为:{tempRes["pv"]}");
+                return false;
+            }
+            DataCentre.GetStorageContent.Factory(OpName).Start(localtion);
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * _liquid.SampleVolume * 0.6)));
+            DataCentre.GetStorageContent.Factory(OpName).Stop(localtion);
+
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start(localtion);
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * _liquid.SampleVolume * 0.4)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop(localtion);
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid1.Total -= 45; //水减去 45毫升
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            return res;
+        }
+
+        /// <summary>
+        /// 标定
+        /// </summary>
+        /// <param name="RouteStep"></param>
+        /// <returns></returns>
+        /// 标定任务需要分两个步骤加液
+        private bool BDStep()
+        {
+            DataCentre.GetStorageContent.Factory("Timer").Start();
+            Thread.Sleep(2000);
+            DataCentre.GetStorageContent.Factory("Timer").Stop();
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start(localtion);
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * _liquid.SampleVolume * 0.5)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop(localtion);
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid1.Total -= 45; //水减去 45毫升
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            return res;
+        }
+    }
+}

+ 14 - 0
SHJX.Service.Control/Route/RouteController/DissolveCoolingOperate.cs

@@ -0,0 +1,14 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class DissolveCoolingOperate : FlowControlOperateImp
+    {
+        private const string EquipmentName = "DissolveCooling";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            return EquipmentName.CoolingTimeCalculate(data.DataManager);
+        }
+    }
+}

+ 110 - 0
SHJX.Service.Control/Route/RouteController/DissolveMoveOperate.cs

@@ -0,0 +1,110 @@
+using System;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using System.Threading.Tasks;
+using SHJX.Service.Common.ReadXML;
+using System.Collections.Generic;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 消解位移动
+    /// </summary>
+    public class DissolveMoveOperate : FlowControlOperateImp
+    {
+        private const string Name = "DissolvePosition";
+        private const string OpName = "Dissolve";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {  
+            var clearPos = Name.GetEquipmentState();
+            var portArgs = new PortArgs
+            {
+                ManualOrAuto = "Auto",
+                TypeName = OpName,
+                Distance = -DataCentre.GetConfig.GetPositionDistance("Dissolve")
+            };
+            switch (data.Task.RouteStep)
+            {
+                case "Clear_In":
+                    MotorSpeed itemSpeed = config.GetSpeed[0];
+                    itemSpeed.Speed = 6;
+                    SetSpeed(itemSpeed);
+                    itemSpeed = config.GetSpeed[1];
+                    itemSpeed.Speed = 6;
+                    SetSpeed(itemSpeed);
+                    List<string> names = new() { "MotorZ", "MotorX", "MotorY" };
+                    names.ForEach(async item =>
+                    {
+                        var res = await XYZGoBack(item);
+                       
+                    });
+                    itemSpeed = config.GetSpeed[0];
+                    SetSpeed(itemSpeed);
+                    itemSpeed = config.GetSpeed[1];
+                    SetSpeed(itemSpeed);
+                    portArgs.WriteWay = WriteWay.Normotopia;
+                    if (clearPos.EquipmentState.Equals(EquipmentEnum.Normotopia))
+                    {
+                        clearPos.EquipmentState = EquipmentEnum.Antiposition;
+                    }
+                    break;
+                case "Clear_Out":
+                    MotorSpeed itemSpeed1 = config.GetSpeed[0];
+                    itemSpeed1.Speed = 6;
+                    SetSpeed(itemSpeed1);
+                    itemSpeed1 = config.GetSpeed[1];
+                    itemSpeed1.Speed = 6;
+                    SetSpeed(itemSpeed1);
+                    List<string> names1 = new() { "MotorZ", "MotorX", "MotorY" };
+                    names1.ForEach(async item =>
+                    {
+                        var res = await XYZGoBack(item);
+
+                    });
+                    itemSpeed1 = config.GetSpeed[0];
+                    SetSpeed(itemSpeed1);
+                    itemSpeed1 = config.GetSpeed[1];
+                    SetSpeed(itemSpeed1);
+                    portArgs.WriteWay = WriteWay.Antiposition;
+                    if (clearPos.EquipmentState.Equals(EquipmentEnum.Antiposition))
+                    {
+                        clearPos.EquipmentState = EquipmentEnum.Normotopia;
+                    }
+                    break;
+                default:
+                    throw new ArgumentNullException(data.Task.RouteStep);
+            }
+            var res = data.Client.Factory(OpName).Write(portArgs);
+            data.DataManager.Update(clearPos);
+            return res;
+        }
+        private async Task<bool> XYZGoBack(string name)
+        {
+            var portArgs = new PortArgs()
+            {
+                ReadWay = "OriginalPosition",
+                TypeName = name,
+                WriteWay = WriteWay.GoBack
+            };
+            
+            var res= (bool)DataCentre.GetClient.Factory(name).Read(portArgs) || DataCentre.GetClient.Factory(name).Write(portArgs);
+            name.UpdateMotorPoint();
+            return res;
+            
+        }
+        public bool SetSpeed(MotorSpeed speed)
+        {
+            #region 速度
+            PortArgs speedArgs = new PortArgs()
+            {
+                TypeName = speed.NodeName,
+                Distance = speed.Speed,
+                WriteWay = WriteWay.Speed
+            };
+            var res = DataCentre.GetClient.Factory(speed.NodeName).Write(speedArgs);
+            #endregion
+
+            return res;
+        }
+    }
+}

+ 188 - 0
SHJX.Service.Control/Route/RouteController/LiquidHeatingOperate.cs

@@ -0,0 +1,188 @@
+using System;
+using System.Linq;
+using SHJX.Service.Dao;
+using System.Globalization;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.ServerClient;
+using SHJX.Service.Model.Control;
+using System.Collections.Generic;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.UserDelegate;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+using SHJX.Service.Common.ReadXML;
+using System.Threading;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class LiquidHeatingOperate : FlowControlOperateImp
+    {
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(LiquidHeatingOperate));
+        private OptClient _client;
+        private OperateDataManager _dataManager;
+        private const string OpName = "Heat";
+        private const string EquipmentName = "HeatRod";
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _client = data.Client;
+            _dataManager = data.DataManager;
+            var portArgs = new PortArgs
+            {
+                TypeName = OpName,
+                Reserve = _dataManager.GetPid("Dissolve")
+            };
+            var status = EquipmentName.GetEquipmentState(); //获取加热棒的状态
+            return status.EquipmentState switch
+            {
+                EquipmentEnum.Stop => StopOperate(portArgs, status),
+                EquipmentEnum.Waiting => WaitiongOperate(portArgs, status),
+                EquipmentEnum.Start => StartOperate(portArgs, status),
+                _ => false,
+            };
+        }
+
+        /// <summary>
+        /// 停止状态
+        /// </summary>
+        /// <param name="portArgs"></param>
+        /// <param name="status"></param>
+        /// <returns></returns>
+        private bool StopOperate(PortArgs portArgs, EquipmentStatus status)
+        {
+            #region 加热
+            var temp = "Step1".GetHeatingTime().Temperature;
+            portArgs.Distance = temp;
+            #endregion
+
+            if (_client.Factory(OpName).Write(portArgs))
+            {
+                Thread.Sleep(5000);
+                logger.LogInformation($"步骤一温度设置成功,温度设置为{temp}");
+                status.Udf1 = "Step1";
+                status.EquipmentState = EquipmentEnum.Waiting;
+                _dataManager.Update(status);
+                var tasks = _dataManager.QueryHeatingTaskbyState("Liquid_Heating");
+                if (tasks.Count > 0)
+                {
+                    var clearNames = (from task in tasks select task.To).ToList();
+                    Messager.Send("clearCycleProcess", clearNames); //这里要做任务通知 加热位通知
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// 执行状态
+        /// </summary>
+        /// <param name="portArgs"></param>
+        /// <param name="status"></param>
+        /// <returns></returns>
+        private bool StartOperate(PortArgs portArgs, EquipmentStatus status)
+        {
+            //计算 当前时间与下发时间的差值
+            var timeSpan = DateTime.Now - status.LastTryTime;
+            var minutes = timeSpan.Hours * 60 + timeSpan.Minutes + timeSpan.Seconds / 60.0;
+            //说明:这里的Udf2记录的是步骤时间之和,由数据库里面查询得到
+            var ringValue = (Convert.ToDouble(status.Udf2) + minutes) / DataCentre.GetHeatingTimeSum();
+            UpdateRings(ringValue);
+            var liquid = status.Udf1?.GetHeatingTime();
+            if (liquid == null) return false;
+            if (minutes < liquid?.HeatingTime) return false;
+            double value = Convert.ToDouble(status.Udf2);
+            value += liquid.HeatingTime;
+            status.Udf2 = value.ToString(CultureInfo.CurrentCulture);
+            //判断条件是,如果当前步骤不等于下一步骤名称,则判定还有剩余任务 并且下一个加热步骤的温度设定大于0
+            if ((!liquid.CurrentStep.Equals(liquid.NextStep)) && (liquid.NextStep.GetHeatingTime().HeatingTime > 0))
+            {
+                #region 加热
+
+                var nextTemperature = liquid.NextStep.GetHeatingTime().Temperature;
+                portArgs.Distance = nextTemperature;
+                var resNext = _client.Factory(OpName).Write(portArgs); //具体加热的步骤
+
+                #endregion
+
+                if (resNext)
+                {
+                    logger.LogInformation($"温度{nextTemperature}下发成功!");
+                    status.EquipmentState = EquipmentEnum.Waiting;
+                    status.Udf1 = liquid.NextStep;
+                    status.LastTryTime = DateTime.MinValue;
+                    _dataManager.Update(status);
+                }
+                return false;
+            }
+
+            #region 停止加热
+
+            portArgs.Distance = -100;
+            var resStop = _client.Factory(OpName).Write(portArgs); //具体加热的步骤
+
+            #endregion
+
+            if (!resStop) return false;
+
+            status.Udf2 = "0";
+            status.Udf1 = string.Empty;
+            status.LastTryTime = DateTime.MinValue;
+            status.EquipmentState = EquipmentEnum.Stop;
+            _dataManager.Update(status);
+            return true;
+        }
+
+        /// <summary>
+        /// 等待状态
+        /// </summary>
+        /// <param name="portArgs"></param>
+        /// <param name="status"></param>
+        /// <returns></returns>
+        private bool WaitiongOperate(PortArgs portArgs, EquipmentStatus status)
+        {
+            long temp = 0;
+            if (_client.Factory(OpName).Read(portArgs) is not Dictionary<string, double> obj) return false;
+            if (Math.Round(obj["sv"]) == -99 || Math.Round(obj["sv"]) == 0)
+            {
+                if (status.Udf1 == "" || status.Udf1 == null)
+                {
+                    temp = "Step1".GetHeatingTime().Temperature;
+                }
+                else
+                {
+                    temp = status.Udf1.GetHeatingTime().Temperature;
+                }
+                portArgs.Distance = temp;
+                _client.Factory(OpName).Write(portArgs);
+            }
+            else
+                temp =Convert.ToInt64( Math.Round(obj["sv"]));
+            logger.LogInformation($"设定温度:{temp},实际温度:{Math.Round(obj["pv"])}");
+            if (temp > Math.Round(obj["pv"])) return false;
+            logger.LogInformation($"温度到达:{Math.Round(obj["pv"])},开始计时保持!");
+            status.EquipmentState = EquipmentEnum.Start;
+            status.LastTryTime = DateTime.Now;
+            _dataManager.Update(status);
+            return false;
+        }
+
+        /// <summary>
+        /// 更新进度条
+        /// </summary>
+        /// <param name="ringValue"></param>
+        private void UpdateRings(double ringValue)
+        {
+            Dictionary<string, object> args = new()
+            {
+                { "value1", ringValue } //value1:传递的是计算得到的进度条的值(传递到主界面中进行更新)
+            };
+            var heatingTasks = _dataManager.QueryHeatingTaskbyState("Liquid_Heating");
+            if (heatingTasks.Count > 0)
+            {
+                var clearNames = (from task in heatingTasks select task.To).ToList();
+                args.Add("value2", clearNames); //value2:传递的为需要更新进度条的消解位的名称(传递到主界面中进行更新)
+            }
+            Messager.Send("ringRadius", args); //更新进度条
+        }
+    }
+}

+ 23 - 0
SHJX.Service.Control/Route/RouteController/LiquidStirOperate.cs

@@ -0,0 +1,23 @@
+using System.Threading;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 搅拌
+    /// </summary>
+    public class LiquidStirOperate : FlowControlOperateImp
+    {
+        private readonly string OpName = "LiquidStir";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            DataCentre.GetStorageContent.Factory(OpName).Start();
+            if (data.Task.TaskType != "补杯")
+            { Thread.Sleep(3 * 1000); }
+            
+            DataCentre.GetStorageContent.Factory(OpName).Stop();
+            return true;
+        }
+    }
+}

+ 103 - 0
SHJX.Service.Control/Route/RouteController/ManiOperate.cs

@@ -0,0 +1,103 @@
+using System;
+using Polly;
+using System.Threading;
+using SHJX.Service.Common.Extend;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.UserDelegate;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class ManiOperate : FlowControlOperateImp
+    {
+        private const string OpName = "Mani";
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            var res = false;
+            var portArgs = new PortArgs
+            {
+                TypeName = OpName
+            };
+            switch (data.Task.RouteStep)
+            {
+                case "Mani_Loosen":
+				if ( DataCentre.GetConfig.ManiGrabFeedBack)
+                    {
+                        var judgeValue = 0;
+                        Policy.HandleResult<bool>(Arg => Arg.Equals(false)).Retry(3, (resultArg, retryCount) =>
+                        {
+                            #region 松开
+                            portArgs.WriteWay = WriteWay.Normotopia;
+                            res = data.Client.Factory(OpName).Write(portArgs);
+                            #endregion
+                            #region 抓取
+                            portArgs.WriteWay = WriteWay.Antiposition;
+                            res = res && data.Client.Factory(OpName).Write(portArgs);
+                            #endregion
+                            judgeValue = retryCount;
+                        }).Execute(() =>
+                        {
+                            portArgs.ReadWay = "Judge";
+                            var judgeRes = Convert.ToBoolean(data.Client.Factory(OpName).Read(portArgs));
+                            #region 任务取消,加入取消缓存
+                            if (judgeValue.Equals(3) && !judgeRes)
+                            {
+                                portArgs.WriteWay = WriteWay.Normotopia;
+                                res = data.Client.Factory(OpName).Write(portArgs);
+                                Messager.Send("getCupError", data.Task);
+                            }
+                            #endregion
+                            res = judgeRes;
+                            return judgeRes;
+                        }); 
+                    }
+                    portArgs.WriteWay = WriteWay.Normotopia;//松开
+                    res = data.Client.Factory(OpName).Write(portArgs);
+                    break;
+                case "Mani_Grab":
+                    portArgs.WriteWay = WriteWay.Antiposition;
+                    res = data.Client.Factory(OpName).Write(portArgs);
+                    if ( DataCentre.GetConfig.ManiGrabFeedBack)
+                    {
+                        var judgeValue = 0;
+                        Policy.HandleResult<bool>(Arg => Arg.Equals(false)).Retry(3, (resultArg, retryCount) =>
+                        {
+                            #region 松开
+                            portArgs.WriteWay = WriteWay.Normotopia;
+                            res = data.Client.Factory(OpName).Write(portArgs);
+                            #endregion
+                            #region 抓取
+                            portArgs.WriteWay = WriteWay.Antiposition;
+                            res = res && data.Client.Factory(OpName).Write(portArgs);
+                            #endregion
+                            judgeValue = retryCount;
+                        }).Execute(() =>
+                        {
+                            portArgs.ReadWay = "Judge";
+                            var judgeRes = Convert.ToBoolean(data.Client.Factory(OpName).Read(portArgs));
+                            #region 任务取消,加入取消缓存
+                            if (judgeValue.Equals(3) && !judgeRes)
+                            {
+                                portArgs.WriteWay = WriteWay.Normotopia;
+                                res = data.Client.Factory(OpName).Write(portArgs);
+                                //CancelCache.Set(data.Task.OriginLocalName, "Cancel");
+                                //data.DataManager.Delete(data.Task);
+                                Messager.Send("getCupError", data.Task);
+                            }
+                            #endregion
+                            res = judgeRes;
+                            return judgeRes;
+                        });
+                    }
+                    break;
+                default:
+                    res = false;
+                    break;
+            }
+            Thread.Sleep(10);
+            return res;
+        }
+    }
+}

+ 53 - 0
SHJX.Service.Control/Route/RouteController/MotorTOperate.cs

@@ -0,0 +1,53 @@
+using System;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class MotorTOperate : FlowControlOperateImp
+    {
+        private const string OpName = "MotorT";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            try
+            {
+                var res = data.Task.RouteStep switch
+                {
+                    "T_Move_L" or "T_Move_W" => SendMove(data),
+                    "T_Move_GoBack_L" or "T_Move_GoBack_W" => SendGoBack(data),
+                    _ => throw new ArgumentNullException(data.Task.RouteStep),
+                };
+                return res;
+            }
+            catch (Exception)
+            {
+                throw;
+            }
+        }
+
+        private bool SendMove(DataEventArgs data)
+        {
+            var portArgs = GetArg(data.Task.To);
+            portArgs.WriteWay = WriteWay.Move;
+            return data.Client.Factory(OpName).Write(portArgs);
+        }
+
+        private bool SendGoBack(DataEventArgs data)
+        {
+            var portArgs = GetArg(data.Task.To);
+            portArgs.WriteWay = WriteWay.GoBack;
+            portArgs.Distance = OpName.MotorFinal();
+            return data.Client.Factory(OpName).Write(portArgs);
+        }
+
+        protected virtual PortArgs GetArg(string to)
+        {
+            return new()
+            {
+                TypeName = OpName,
+                Distance = to.CalculateAreaPoint(OpName)
+            };
+        }
+    }
+}

+ 33 - 0
SHJX.Service.Control/Route/RouteController/MotorXOperate.cs

@@ -0,0 +1,33 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class MotorXOperate : FlowControlOperateImp
+    {
+        private const string OpName = "MotorX";
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            var res = data.Task.RouteStep switch
+            {
+                "X_Move_From" => SetModbusValue(data, data.Task.From),
+                "X_Move_To" => SetModbusValue(data, data.Task.To),
+                _ => false
+            };
+            return res;
+        }
+
+        private static bool SetModbusValue(DataEventArgs data, string local)
+        {
+            PortArgs portArgs = new()
+            {
+                WriteWay = WriteWay.Move,
+                TypeName = OpName,
+                Distance = local.CalculateAreaPoint(OpName)
+            };
+            return data.Client.Factory(OpName).Write(portArgs);
+        }
+    }
+}

+ 43 - 0
SHJX.Service.Control/Route/RouteController/MotorYOperate.cs

@@ -0,0 +1,43 @@
+using System;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using System.Linq;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class MotorYOperate : FlowControlOperateImp
+    {
+        private const string OpName = "MotorY";
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            var res = data.Task.RouteStep switch
+            {
+                "Y_Move_From" => SetSerialPortData(data, data.Task.From),
+                "Y_Move_To" => SetSerialPortData(data, data.Task.To),
+                _ => throw new ArgumentNullException(data.Task.RouteStep),
+            };
+            return res;
+        }
+
+        private static bool SetSerialPortData(DataEventArgs data, string local)
+        {
+            PortArgs portArgs = new()
+            {
+                WriteWay = WriteWay.Move,
+                TypeName = OpName,
+                Distance = local.CalculateAreaPoint(OpName)
+            };
+            if (data.Client.Factory(OpName).Write(portArgs))
+            {
+                var motor = DataCentre.GetMotor.First(item => item.EquipmentName.Equals(OpName));
+                if (motor is null) throw new ArgumentNullException(OpName);
+                motor.FinalPoint += -portArgs.Distance / motor.ConvertRatio;
+                var row = DataCentre._dataManager.Update(motor);
+                return true;
+            }
+            return false;
+        }
+    }
+}

+ 82 - 0
SHJX.Service.Control/Route/RouteController/MotorZOperate.cs

@@ -0,0 +1,82 @@
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Model.Control;
+using SHJX.Service.Common.Extend;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    public class MotorZOperate : FlowControlOperateImp
+    {
+        private const string OpName = "MotorZ";
+        public static bool AxisZdownOrUp = false;//z轴上下状态
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            var res = data.Task.RouteStep switch
+            {
+                "Z_Move_Down_From" => SetSerialPortData(data, data.Task.From), //Z轴下降
+                "Z_Move_Down_To" => SetSerialPortData(data, data.Task.To, isdeduct: true),
+                "Z_Move_GoBack_From" or "Z_Move_GoBack_To" => MotorZGoBack(data), //Z轴上升
+                _ => false
+            };
+            return res;
+        }
+
+        /// <summary>
+        /// Z轴下降
+        /// </summary>
+        /// <param name="data"></param>
+        /// <param name="local">地址</param>
+        /// <param name="isdeduct">是否扣减</param>
+        /// <returns></returns>
+        protected virtual bool SetSerialPortData(DataEventArgs data, string local, bool isdeduct = false)
+        {
+            //这里要做判断其实
+            //判断X、Y是否已经到达了该点,并且是否Z轴在原点的位置上
+            var movelenght = local.CalculateAreaPoint(OpName);
+            if (isdeduct)
+            {
+                AxisZdownOrUp = true;
+                movelenght += 10000;
+            }
+            else
+                AxisZdownOrUp = false;
+            var portArgs = new PortArgs()
+            {
+                WriteWay = WriteWay.Move,
+                TypeName = OpName,
+                Distance = movelenght
+            };
+            
+            return data.Client.Factory(OpName).Write(portArgs);
+        }
+
+        /// <summary>
+        /// Z轴返回原点
+        /// </summary>
+        /// <returns></returns>
+        public bool MotorZGoBack(DataEventArgs  data)
+        {
+            PortArgs portArgs = new()
+            {
+                WriteWay = WriteWay.GoBack,
+                TypeName = OpName,
+                Distance = OpName.MotorFinal()
+            };
+            var res = data.Client.Factory(OpName).Write(portArgs);
+            AxisZdownOrUp = true;
+            if (CancelCache.Contains(data.Task.OriginLocalName)&&data.Task.RouteType!="SP")
+            {
+                if (CancelCache.Get(data.Task.OriginLocalName).Equals("Cancel"))
+                {
+                    var task = data.Task;
+                    task.Status = TaskState.Cancel;
+                    data.DataManager.Update(task);
+                    CancelCache.Remove(data.Task.OriginLocalName);
+                }
+            }
+            return res;
+        }
+    }
+}

+ 72 - 0
SHJX.Service.Control/Route/RouteController/SampleAL1Operate.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+using System.Threading;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.ReadXML;
+
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 硫酸汞加液
+    /// </summary>
+    public class SampleAL1Operate : FlowControlOperateImp
+    {
+        private const string OpName = "Mercury";
+        private DropLiquid _liquid;
+        private EquipmentTask _task;
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _task = data.Task;
+            _liquid = data.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable) return true;
+            DataCentre.GetStorageContent.Factory("Timer").Start();
+            Thread.Sleep(2000);
+            DataCentre.GetStorageContent.Factory("Timer").Stop();
+
+            var res = data.Task.RouteType switch
+            {
+                "Wash" => WashLiquid(),
+                _ => OtherAddLiquid(),
+            };
+            return res;
+        }
+
+        /// <summary>
+        /// 清洗 && 润洗
+        /// </summary>
+        /// <returns></returns>
+        private bool WashLiquid()
+        {
+            var value = _task.TaskType switch
+            {
+                "润洗" => _liquid.WashVolume,
+                "清洗" => _liquid.ClearVolume,
+                _ => throw new ArgumentNullException(_task.TaskType),
+            };
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start();
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * value)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop();
+            LiquidTotal liquid = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid.Total -= 5; // 硫酸汞加液扣减5毫升
+            res = res & DataCentre._dataManager.Update<LiquidTotal>(liquid) > 0;
+            return res;
+        }
+
+        /// <summary>
+        /// 其他加液
+        /// </summary>
+        /// <returns></returns>
+        private bool OtherAddLiquid()
+        {
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start();
+            Thread.Sleep(Convert.ToInt32(Math.Round(_liquid.SampleVolume * 1000)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop();
+            LiquidTotal liquid = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid.Total -= 2; // 硫酸汞加液扣减2毫升
+            res = res & DataCentre._dataManager.Update<LiquidTotal>(liquid) > 0;
+            return res;
+        }
+    }
+}

+ 94 - 0
SHJX.Service.Control/Route/RouteController/SampleAL2Operate.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Linq;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 加液
+    /// </summary>
+    /// 重铬酸钾
+    public class SampleAL2Operate : FlowControlOperateImp
+    {
+        private string _opName;
+        private double _dropAmount;
+        private DropLiquid _liquid;
+        private EquipmentTask _task;
+
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _task = data.Task;
+            _liquid = data.DataManager.QueryLiquid("PotassiumDichromate");
+            if (!_liquid.Enable) return true;
+            var res = _task.RouteType switch
+            {
+                "Wash" => WashLiquid(),
+                _ => OtherAddLiquid(),
+            };
+            return res;
+        }
+
+        /// <summary>
+        /// 清洗 && 润洗
+        /// </summary>
+        /// <returns></returns>
+        private bool WashLiquid()
+        {
+
+            string[] types = new string[] { "PotassiumDichromate_Low", "PotassiumDichromate_High" };
+            bool res = false;
+            foreach (var item in types)
+            {
+                DataCentre.GetPumpContent.Factory(item).GoBackOriginalPosition("Out");
+                var value = _task.TaskType switch
+                {
+                    "润洗" => _liquid.WashVolume,
+                    "清洗" => _liquid.ClearVolume,
+                    _ => throw new ArgumentNullException(_task.TaskType),
+                };
+                var DropOnceAmount = DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("PotassiumDichromate"));
+                double dropAmount = item switch
+                {
+                    "PotassiumDichromate_Low" => DropOnceAmount.LowValue,
+                    "PotassiumDichromate_High" => DropOnceAmount.HighValue,
+                    _ => throw new ArgumentNullException(_task.TaskType),
+                };
+                res = DataCentre.GetPumpContent.Factory(item).LiquidOperation("In", Convert.ToInt64(Math.Round(value / dropAmount * 20000 * -1)));
+                res = res && DataCentre.GetPumpContent.Factory(item).LiquidOperation("Out", Convert.ToInt64(Math.Round(value / dropAmount * 20000)));
+                LiquidTotal liquid = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(item)).First();
+                liquid.Total -= 5; //重铬酸钾减5毫升
+                res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid) > 0;
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// 其他加液
+        /// </summary>
+        /// <returns></returns>
+        private bool OtherAddLiquid()
+        {
+            GetOpName();
+            DataCentre.GetPumpContent.Factory(_opName).GoBackOriginalPosition("In");
+            var arriveEndpoint = DataCentre.GetPumpContent.Factory(_opName).ArriveEndPoint();
+            if (arriveEndpoint) DataCentre.GetPumpContent.Factory(_opName).LiquidOperation("In", Convert.ToInt64(Math.Round(_liquid.SampleVolume / _dropAmount * 20000 * -1)));
+            var res = DataCentre.GetPumpContent.Factory(_opName).LiquidOperation("Out", Convert.ToInt64(Math.Round((_liquid.SampleVolume / _dropAmount) * 20000)));
+            LiquidTotal liquid = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(_opName)).First();
+            liquid.Total -= 5; //重铬酸钾减5毫升
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid) > 0;
+            return res;
+
+        }
+            private void GetOpName()
+        {
+            (_opName, _dropAmount) = _task.SampleConcentration switch
+            {
+                "Low" => ("PotassiumDichromate_Low", DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("PotassiumDichromate")).LowValue),
+                "High" => ("PotassiumDichromate_High", DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("PotassiumDichromate")).HighValue),
+                _ => throw new ArgumentNullException(_task.SampleConcentration),
+            };
+        }
+    }
+}

+ 65 - 0
SHJX.Service.Control/Route/RouteController/SampleCoolingOperate.cs

@@ -0,0 +1,65 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.ReadXML;
+using SHJX.Service.Model.Control;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 样品位冷却
+    /// </summary>
+    /// 这一步是放在搅拌结束后做的
+    public class SampleCoolingOperate : FlowControlOperateImp
+    {
+        private const string EquipmentName = "SampleCooling";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            MotorSpeed itemSpeed = config.GetSpeed[0];
+            itemSpeed.Speed = 6;
+            SetSpeed(itemSpeed);
+            itemSpeed = config.GetSpeed[1];
+            itemSpeed.Speed = 6;
+            SetSpeed(itemSpeed);
+            List<string> names = new() { "MotorZ", "MotorX", "MotorY" };
+            names.ForEach(async item =>
+            {
+                var res = await XYZGoBack(item);
+
+            });
+            itemSpeed = config.GetSpeed[0];
+            SetSpeed(itemSpeed);
+            itemSpeed = config.GetSpeed[1];
+            SetSpeed(itemSpeed);
+            return EquipmentName.CoolingTimeCalculate(data.DataManager);
+        }
+        private async Task<bool> XYZGoBack(string name)
+        {
+            var portArgs = new PortArgs()
+            {
+                ReadWay = "OriginalPosition",
+                TypeName = name,
+                WriteWay = WriteWay.GoBack
+            };
+
+            var res = (bool)DataCentre.GetClient.Factory(name).Read(portArgs) || DataCentre.GetClient.Factory(name).Write(portArgs);
+            name.UpdateMotorPoint();
+            return res;
+
+        }
+        public bool SetSpeed(MotorSpeed speed)
+        {
+            #region 速度
+            PortArgs speedArgs = new PortArgs()
+            {
+                TypeName = speed.NodeName,
+                Distance = speed.Speed,
+                WriteWay = WriteWay.Speed
+            };
+            var res = DataCentre.GetClient.Factory(speed.NodeName).Write(speedArgs);
+            #endregion
+
+            return res;
+        }
+    }
+}

+ 79 - 0
SHJX.Service.Control/Route/RouteController/TitrationAddIndicatorOperate.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Threading;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+using System.Linq;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 加指示剂
+    /// </summary>
+    /// 试亚铁灵
+    /// 
+    public class TitrationAddIndicatorOperate : FlowControlOperateImp
+    {
+        private  string OpName = "Indicator";
+        private string DripNozzleType = "DripNozzle";
+        private DropLiquid _liquid;
+        private EquipmentTask _task;
+        //public static bool HighFlag = false;
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _task = data.Task;
+            //if (_task.SampleConcentration == "High" || data.Task.To == "D4")
+            //{
+            //    OpName = "Indicator2";
+            //    DripNozzleType = "DripNozzle2";
+            //    HighFlag = true;
+            //}
+            //else
+            //    HighFlag = false;
+            _liquid = data.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable)
+            {
+                var dripNozzleRes = DataCentre.GetStorageContent.Factory(DripNozzleType).Start();
+                return dripNozzleRes;
+            }
+            DataCentre.GetStorageContent.Factory(DripNozzleType).Start();
+            DataCentre.GetStorageContent.Factory("Timer").Start();
+            Thread.Sleep(2000);
+            DataCentre.GetStorageContent.Factory("Timer").Stop();
+            var res = _task.RouteType switch
+            {
+                "Wash" => WashLiquid(),
+                _ => OtherAddLiquid(),
+            };
+            return res;
+        }
+
+        private bool WashLiquid()
+        {
+            var value = _task.TaskType switch
+            {
+                "润洗" => _liquid.WashVolume,
+                "清洗" => _liquid.ClearVolume,
+                _ => throw new ArgumentNullException(_task.TaskType),
+            };
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start();
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * value)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop();
+            LiquidTotal liquid = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid.Total -= 5;//指示剂INDECATOR5毫升
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid) > 0;
+            return res;
+        }
+
+        private bool OtherAddLiquid()
+        {
+            var res = DataCentre.GetStorageContent.Factory(OpName).Start();
+            Thread.Sleep(Convert.ToInt32(Math.Round(1000 * _liquid.SampleVolume)));
+            res = res && DataCentre.GetStorageContent.Factory(OpName).Stop();
+            LiquidTotal liquid = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(OpName)).First();
+            liquid.Total -= 0.2;//指示剂INDECATOR0.2毫升
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid) > 0;
+            return res;
+        }
+    }
+}

+ 557 - 0
SHJX.Service.Control/Route/RouteController/TitrationOperate.cs

@@ -0,0 +1,557 @@
+using System;
+using System.Linq;
+using SHJX.Service.Dao;
+using System.Threading;
+using System.Diagnostics;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Model.Control;
+using System.Collections.Generic;
+using SHJX.Service.Common.Camera;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.Calculate;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.UserDelegate;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 开始滴定
+    /// </summary>
+    /// 硫酸亚铁铵
+    public class TitrationOperate : FlowControlOperateImp
+    {
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(TitrationOperate));
+        private const string OpName = "FAS";
+        private int _signalTimeLen;
+        private int _titrationCount;
+        private double _amountValue;
+        private DropLiquid _liquid;
+        private ColorDataGraber _graber;
+        private WriteWay _concentrationType;
+        private TitrationValue _balanceVals;
+        private OperateDataManager _dataManager;
+        private string DripNozzleType = "DripNozzle";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _graber = data.LiquidDrop.Graber;
+            //if (data.Task.SampleConcentration == "High" || data.Task.To == "D4")
+            //    { DripNozzleType = "DripNozzle2";
+            //    _graber = data.LiquidDrop2.Graber;
+            //}
+            _liquid = data.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable)
+            {
+                var dripNozzleRes = DataCentre.GetStorageContent.Factory(DripNozzleType).Stop();
+                return dripNozzleRes;
+            }
+           
+            _signalTimeLen = 10;
+            _titrationCount = 0;
+            _dataManager = data.DataManager;
+            _balanceVals = DataCentre.GetBalanceValue;
+            var res = data.Task.RouteType switch
+            {
+                "Wash" => WashLiquid(data),
+                _ => SampleDropLiquid(data),
+            };
+            DataCentre.GetStorageContent.Factory(DripNozzleType).Stop(); // WriteDrip(data);
+            return res;
+        }
+
+        /// <summary>
+        /// 润洗 && 清洗
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private bool WashLiquid(DataEventArgs data)
+        {
+            logger.LogInformation("进入润洗(清洗)流程");
+            var value = data.Task.TaskType switch
+            {
+                "润洗" => _liquid.WashVolume,
+                "清洗" => _liquid.ClearVolume,
+                _ => throw new ArgumentNullException(data.Task.TaskType),
+            };
+            string[] types = new string[] { "Low", "High" };
+            bool res = true;
+            foreach (var item in types)
+            {
+                (_concentrationType, _amountValue) = item switch
+                {
+                    "Low" => (WriteWay.Normotopia, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).LowValue),
+                    "High" => (WriteWay.Antiposition, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).HighValue),
+                    _ => throw new ArgumentNullException(data.Task.SampleConcentration),
+                };
+                ConvertConcentration(_concentrationType);
+                var distance = Convert.ToInt64(Math.Round(value / _amountValue * 20000));
+                var portArgs = new PortArgs()
+                {
+                    TypeName = OpName,
+                    WriteWay = WriteWay.Move,
+                    Distance = distance
+                };
+                if (data.Task.TaskType== "清洗")
+                {
+                    res = res && DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    Thread.Sleep((int)(distance / 20000 * 600));
+                }
+                if (data.Task.TaskType == "润洗")
+                {
+                    portArgs.Distance *= -1;
+                    res = res && DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    Thread.Sleep((int)(distance / 20000 * 600));
+
+                    portArgs.Distance *= -11/10;
+                    res = res && DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    Thread.Sleep((int)(distance / 20000 * 700));
+                }
+                string liquidname = "FAS";
+                if (data.Task.SampleConcentration == "High")
+                {
+                    liquidname = "FAS2";
+                }
+                LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(liquidname)).First();
+                liquid1.Total -= value; //硫酸亚铁铵(高)减去20体积
+                res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            }
+            
+            logger.LogInformation("润洗(清洗)流程完成");
+            return res;
+        }
+
+        /// <summary>
+        /// 液体滴定
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private bool SampleDropLiquid(DataEventArgs data)
+        {
+            (_concentrationType, _amountValue) = data.Task.SampleConcentration switch
+            {
+                "Low" => (WriteWay.Normotopia, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).LowValue),
+                "High" => (WriteWay.Antiposition, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).HighValue),
+                _ => throw new ArgumentNullException(data.Task.SampleConcentration),
+            };
+            ConvertConcentration(_concentrationType);
+            var res = true;
+           
+            data.LiquidDrop.Camera.OpenCamera();
+            using (TitrationStirOperate ts = new())
+            {
+                if (ts.StartStir)
+                {
+                    Thread.Sleep(1000 * _balanceVals.BeforeWaitTime);//滴定之前等待时间
+                    var task = data.Task;
+                    AdjustCamera(task.SampleConcentration);
+                    StartDropLiquid(ref task);
+                        
+                    data.Task = task;
+                    CalculateResult(task); //滴定完成需做计算
+                }
+                else
+                {
+                    res = ts.StartStir;
+                }
+            }
+            string liquidname = "FAS";
+            if (data.Task.SampleConcentration == "High")
+            {
+                liquidname = "FAS2";
+            }
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(liquidname)).First();
+            liquid1.Total -= data.Task.Amount; //硫酸亚铁铵(低)减去滴定体积
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            
+            
+            Messager.Send("DropEnd");
+            return res;
+        }
+
+        /// <summary>
+        /// 液体滴定
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public  bool ManualSampleDropLiquid(Dictionary<string ,string>  data1)
+        {
+            EquipmentTask task1 = new EquipmentTask();
+            var data2 = task1.GetDataArgs();
+            data2.Task.QuickTitration = Convert.ToBoolean(data1["IfFastDiding"]);
+            _graber = data2.LiquidDrop.Graber;
+            _liquid = data2.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable)
+            {
+                var dripNozzleRes = DataCentre.GetStorageContent.Factory(DripNozzleType).Stop();
+                return dripNozzleRes;
+            }
+
+            _signalTimeLen = 10;
+            _titrationCount = 0;
+            _dataManager = data2.DataManager;
+            _balanceVals = DataCentre.GetBalanceValue;
+            _balanceVals.LowValue = Convert.ToDouble(data1["YuZhiValue"]);
+            _balanceVals.HighValue = Convert.ToDouble(data1["YuZhiValue"]);
+            _balanceVals.MaxVolume = Convert.ToDouble(data1["MaxTiJiValue"]);
+            _balanceVals.AfterWaitTime = Convert.ToInt32(data1["CheckTimeValue"]);
+            _balanceVals.QuickTitrationValue = Convert.ToDouble(data1["FastDidingValue"]);
+
+            data2.Task.SampleConcentration = data1["NongDuValue"] switch
+            {
+                "低" =>"Low",
+                "高" =>"High",
+                _ => throw new ArgumentNullException(data1["NongDuValue"]),
+            };
+            (_concentrationType, _amountValue) = data2.Task.SampleConcentration switch
+            {
+                "Low" => (WriteWay.Normotopia, Convert.ToDouble(data1["DropAmountValue"])),
+                "High" => (WriteWay.Antiposition, Convert.ToDouble(data1["DropAmountValue"])),
+                _ => throw new ArgumentNullException(data2.Task.SampleConcentration),
+            };
+            ConvertConcentration(_concentrationType);
+            var res = true;
+            data2.LiquidDrop.Camera.OpenCamera();
+            using (TitrationStirOperate ts = new())
+            {
+                if (ts.StartStir)
+                {
+                    Thread.Sleep(1000 * Convert.ToInt32( data1["DengDaiTimeValue"] ));//滴定之前等待时间
+                    var task = data2.Task;
+                    AdjustCamera(task.SampleConcentration);
+                    StartDropLiquid(ref task);
+                    data2.Task = task;
+                    CalculateResult(task); //滴定完成需做计算
+                }
+                else
+                {
+                    res = ts.StartStir;
+                }
+            }
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals("FAS")).First();
+            liquid1.Total -= data2.Task.Amount; //硫酸亚铁铵(低)减去滴定体积
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            Messager.Send("DropEnd");
+            Messager.Send("DropEndWindows");
+            return res;
+        }
+        /// <summary>
+        /// 滴定开始
+        /// </summary>
+        private void StartDropLiquid(ref EquipmentTask task)
+        {
+            try
+            {
+                var jumpCount = 0; //突跃次数
+                var slowSpeedCount = 0; //减速次数
+                var isRunning = true;
+                var balanceVal = GetBalanceVal(task);
+                var currSignals = new List<ColorDataPoint>(); // 信号队列
+                for (var i = 0; i < 7; i++)
+                {
+                    currSignals.Add(_graber.GrabSignal(500)); // 采集7段信号作为背景
+                }
+                PortArgs portArgs = new()
+                {
+                    TypeName = OpName,
+                    WriteWay = WriteWay.Move
+                };
+                //快速滴定
+                if (task.QuickTitration)
+                {
+                    var quickValue = Convert.ToInt32(Math.Round(_balanceVals.QuickTitrationValue / _amountValue * 20000));
+                    portArgs.Distance = quickValue;
+                    var res = true;
+                    do
+                    {
+                        res = DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                        if (res)
+                        {
+                            task.Amount += _balanceVals.QuickTitrationValue;
+                        }
+                        logger.LogInformation($"快速滴定一次性写入{quickValue},写入{(res ? "成功" : "失败")}");
+                    } while (!res);
+                    Thread.Sleep(5 * 1000);
+                    logger.LogInformation("等待五秒");
+                }
+                while (true)
+                {
+                    if (!isRunning) break;
+                    if (task.Amount >= _balanceVals.MaxVolume) break;
+                    #region 滴液
+                    portArgs.Distance = 20000;
+                    var res = DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    if (!res)
+                    {
+                        logger.LogInformation("写入失败,等待两秒后再次写入");
+                        Thread.Sleep(2000);
+                        continue;
+                    }
+                    _titrationCount += 1;
+                    task.Amount += _amountValue;
+
+                    #endregion
+
+                    #region 摄像头采集信号
+
+                    if (_titrationCount.Equals(currSignals.Count - 7))
+                    {
+                        currSignals[currSignals.Count - 1] = _graber.GrabSignal(_signalTimeLen);
+                    }
+                    else
+                    {
+                        currSignals.Add(_graber.GrabSignal(_signalTimeLen));
+                    }
+
+                    #endregion
+
+                    #region 颜色信号判断
+
+                    if (currSignals.Count >= 7) // 信号判断
+                    {
+                        var currpt = currSignals.Count - 7; // 计算起点
+                        var subB = (currSignals[currpt + 6].GetBSV() - (currSignals[currpt + 0].GetBSV() +
+                            currSignals[currpt + 1].GetBSV()) / 2) * 100 / currSignals[currpt + 6].GetBSV();
+                        logger.LogInformation($"当前第{_titrationCount:D3}滴,体积为:{ task.Amount:F3},滴定阈值获取为{subB:F3}");
+                        Dictionary<string, double> dropValueArg = new()
+                        {
+                            { "Count", _titrationCount },
+                            { "Amount", task.Amount },
+                            { "BlueValue", subB }
+                        };
+                        Messager.Send("DropValue", dropValueArg);
+                        if (subB <= balanceVal)
+                        {
+                            jumpCount++;
+                            var swten = new Stopwatch();
+                            swten.Start();
+                            var retreatCount = 1; //后退次数
+                            while (true) // 继续采集信号,监测是否褪色
+                            {
+                                currSignals[currpt + 6] = _graber.GrabSignal(_signalTimeLen);
+                                subB = (currSignals[currpt + 6].GetBSV() -
+                                        (currSignals[currpt + 0].GetBSV() + currSignals[currpt + 1].GetBSV()) / 2) *
+                                    100 / currSignals[currpt + 6].GetBSV();
+                                logger.LogInformation($"检测已到达设定阈值,观察是否褪色,观察阈值为{subB:F3}");
+                                if (subB <= balanceVal * 0.7)
+                                {
+                                    retreatCount++;
+                                    if (retreatCount > _balanceVals.AfterWaitTime)
+                                    {
+                                        break;
+                                    }
+                                    isRunning = false;
+                                    Thread.Sleep(1000);
+                                    continue; // 未褪色,继续监测
+                                }
+                                isRunning = true; //当检测到的信号值 大于阈值时,则滴定未结束,需要继续滴定
+                                if (retreatCount > 3)
+                                {
+                                    slowSpeedCount = 10; //如果回退超过三次判定,则给定慢速10次
+                                }
+
+                                break;
+                            }
+
+                            swten.Stop();
+                        }
+                        else
+                        {
+                            jumpCount = jumpCount > 0 ? jumpCount - 1 : 0;
+                            slowSpeedCount = slowSpeedCount > 0 ? slowSpeedCount - 1 : slowSpeedCount;
+                        }
+                    }
+
+                    #endregion
+
+                    Thread.Sleep((jumpCount <= 1 && slowSpeedCount <= 1) ? 800 : 3000);
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 获取液体滴定阈值
+        /// </summary>
+        private double GetBalanceVal(EquipmentTask task)
+        {
+            var balanceVal = task.SampleConcentration switch
+            {
+                "Low" => _balanceVals.LowValue * -1,
+                "High" => _balanceVals.HighValue * -1,
+                _ => throw new ArgumentNullException(task.SampleConcentration)
+            };
+            return balanceVal;
+        }
+
+        /// <summary>
+        /// 摄像头调整
+        /// </summary>
+        private void AdjustCamera(string level)
+        {
+            _graber.IsNoFiredEvent = true; // 调节曝光参数
+            if (TitrationSettings.SensorPara.UseAutoUpdateRoiPos)
+            {
+                _graber.UpdateRoiPos(writeFlag: false);
+            }
+            switch (level)
+            {
+                case "Low":
+                    _graber.PresetLightParam();
+                    break;
+                case "High":
+                    _graber.WebCam.SetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, -3, AForge.Video.DirectShow.CameraControlFlags.Manual);
+                    Thread.Sleep(2000);
+                    _graber.WebCam.GetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, out var value, out var flags);
+                    logger.LogInformation($"高浓度设置了曝光值 ---> -2,当前曝光值为:{value}");
+                    break;
+            }
+            _graber.IsNoFiredEvent = false;
+        }
+
+
+        /// <summary>
+        /// 切换高低浓度
+        /// </summary>
+        /// <param name="way"></param>
+        private bool ConvertConcentration(WriteWay way)
+        {
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = way
+            };
+            return DataCentre.GetClient.Factory(OpName).Write(switchArg);
+        }
+        private static bool ConvertConcentrationM(WriteWay way)
+        {
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = way
+            };
+            return DataCentre.GetClient.Factory(OpName).Write(switchArg);
+        }
+
+        /// <summary>
+        /// 计算结果
+        /// </summary>
+        private bool CalculateResult(EquipmentTask task)
+        {
+            try
+            {
+                task.Result = task.TaskType switch
+                {
+                    "水样" => GetSampleResult(task),
+                    "空白" => 0,
+                    "标定" => 0,
+                    _ => throw new ArgumentNullException(task.TaskType),
+                };
+                task.Result = Math.Round(task.Result, 2);
+                //这里是要在界面显示的样子
+                
+                task.Result= task.Result < 0 ? 0 : task.Result;
+                task.Udf1 = task.SampleConcentration.Equals("Low") ? 0 < task.Result && task.Result < 4 ? "ND" : task.Result.ToString() : task.Result.ToString();
+                //task.Udf1 = task.Result.ToString();
+                task.Amount = Convert.ToDouble(Math.Round((decimal)task.Amount, 2));
+                _dataManager.Update(task);
+                var resultArg = new Dictionary<string, string>
+                {
+                    {"LocalName", task.OriginLocalName},
+                    {"Volume",  Math.Round((decimal)task.Amount, 2).ToString()},
+                    {"Result", task.Udf1}
+                };
+                //通知更新
+                Messager.Send("SampleResult", resultArg);
+                return true;
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 获取水样的结果
+        /// </summary>
+        /// <param name="task"></param>
+        /// <returns></returns>
+        /// 计算公式 :ρ=((V * ( V0 - V1 ) * 8000 )/ V2 )* f
+        public double GetSampleResult(EquipmentTask task)
+        {
+            var blankTasks = _dataManager.GetTaskByWaveKey(task.WaveKey, task.SampleConcentration, "空白") ?? _dataManager.GetLastOtherTaskByWaveKey("空白", task.SampleConcentration);
+            SampleCalculate.tempCount = 0;
+            blankTasks.GeBlankTaskResult(_balanceVals.MaxVolume, out var noneValue);
+            var bdTasks = _dataManager.GetTaskByWaveKey(task.WaveKey, task.SampleConcentration, "标定") ?? _dataManager.GetLastOtherTaskByWaveKey("标定", task.SampleConcentration);
+            bdTasks.GetBDTaskResult(_balanceVals.MaxVolume, task.SampleConcentration, out var bdValue);
+            //var bdValue = 0.0;
+            var Taskvolumn = Math.Round(task.Amount, 2);
+            //计算参数 C:硫酸亚铁铵浓度
+            double avgValue = bdValue * (noneValue - Taskvolumn) * 8000 / 10 * task.GetSampleMultiple;
+            return avgValue;
+        }
+
+#if false
+        /// <summary>
+        /// 获取空白样的结果
+        /// </summary>
+        /// <param name="noneTasks"></param>
+        /// <param name="noneValue"></param>
+        private void GetNoneTaskResult(List<EquipmentTask> noneTasks, out double noneValue)
+        {
+            if (noneTasks?.Count > 1)
+            {
+                var noneMaxValue = noneTasks.Max(item => item.Amount);
+                var noneMinValue = noneTasks.Min(item => item.Amount);
+                if (noneMaxValue - noneMinValue <= 0.5)
+                {
+                    noneValue = noneTasks.Average(item => item.Amount);
+                    return;
+                }
+
+                if (noneMaxValue >= _balanceVals.MaxVolume)
+                {
+                    GetNoneTaskResult(noneTasks.Where(item => item.Amount != noneMaxValue).ToList(), out noneValue);
+                }
+                else
+                {
+                    noneValue = noneMaxValue;
+                }
+            }
+            else
+            {
+                var first = noneTasks?.FirstOrDefault();
+                noneValue = first?.Amount ?? 0;
+            }
+        }
+#endif
+
+        public void CheckCamera(string level,string final)
+        {
+            EquipmentTask task1 = new EquipmentTask();
+            var data2 = task1.GetDataArgs();
+            _graber = data2.LiquidDrop.Graber;
+            _graber.IsNoFiredEvent = true; // 调节曝光参数
+            if (TitrationSettings.SensorPara.UseAutoUpdateRoiPos)
+            {
+                _graber.UpdateRoiPos(writeFlag: false);
+            }
+            switch (level)
+            {
+                case "低":
+                    _graber.PresetLightParammanual(Convert.ToInt32(final));
+                    break;
+                case "High":
+                    _graber.WebCam.SetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, -3, AForge.Video.DirectShow.CameraControlFlags.Manual);
+                    Thread.Sleep(2000);
+                    _graber.WebCam.GetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, out var value, out var flags);
+                    logger.LogInformation($"高浓度设置了曝光值 ---> -2,当前曝光值为:{value}");
+                    break;
+            }
+            _graber.IsNoFiredEvent = false;
+        }
+    }
+}

+ 532 - 0
SHJX.Service.Control/Route/RouteController/TitrationOperate3.cs

@@ -0,0 +1,532 @@
+using System;
+using System.Linq;
+using SHJX.Service.Dao;
+using System.Threading;
+using System.Diagnostics;
+using SHJX.Service.Model.Dao;
+using SHJX.Service.Model.Control;
+using System.Collections.Generic;
+using SHJX.Service.Common.Camera;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Common.Calculate;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Common.UserDelegate;
+using SHJX.Service.Common.Logging;
+using Microsoft.Extensions.Logging;
+using SHJX.Service.Common.ReadXML;
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 开始滴定
+    /// </summary>
+    /// 硫酸亚铁铵
+    public class TitrationOperate : FlowControlOperateImp
+    {
+        private static readonly ILogger logger = LogFactory.BuildLogger(typeof(TitrationOperate));
+        private const string OpName = "FAS";
+        private int _signalTimeLen;
+        private int _titrationCount;
+        private double _amountValue;
+        private DropLiquid _liquid;
+        private ColorDataGraber _graber;
+        private WriteWay _concentrationType;
+        private TitrationValue _balanceVals;
+        private OperateDataManager _dataManager;
+        private string DripNozzleType = "DripNozzle";
+        public virtual bool Operate(ReadConfigUtil config, DataEventArgs data)
+        {
+            _graber = data.LiquidDrop.Graber;
+            //if (data.Task.SampleConcentration == "High" || data.Task.To == "D4")
+            //    { DripNozzleType = "DripNozzle2";
+            //    _graber = data.LiquidDrop2.Graber;
+            //}
+            _liquid = data.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable)
+            {
+                var dripNozzleRes = DataCentre.GetStorageContent.Factory(DripNozzleType).Stop();
+                return dripNozzleRes;
+            }
+           
+            _signalTimeLen = 10;
+            _titrationCount = 0;
+            _dataManager = data.DataManager;
+            _balanceVals = DataCentre.GetBalanceValue;
+            var res = data.Task.RouteType switch
+            {
+                "Wash" => WashLiquid(data),
+                _ => SampleDropLiquid(data),
+            };
+            DataCentre.GetStorageContent.Factory(DripNozzleType).Stop(); // WriteDrip(data);
+            return res;
+        }
+
+        /// <summary>
+        /// 润洗 && 清洗
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private bool WashLiquid(DataEventArgs data)
+        {
+            logger.LogInformation("进入润洗(清洗)流程");
+            var value = data.Task.TaskType switch
+            {
+                "润洗" => _liquid.WashVolume,
+                "清洗" => _liquid.ClearVolume,
+                _ => throw new ArgumentNullException(data.Task.TaskType),
+            };
+            string[] types = new string[] { "Low", "High" };
+            bool res = true;
+            foreach (var item in types)
+            {
+                (_concentrationType, _amountValue) = item switch
+                {
+                    "Low" => (WriteWay.Normotopia, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).LowValue),
+                    "High" => (WriteWay.Antiposition, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).HighValue),
+                    _ => throw new ArgumentNullException(data.Task.SampleConcentration),
+                };
+                ConvertConcentration(_concentrationType);
+                var distance = Convert.ToInt64(Math.Round(value / _amountValue * 20000));
+                var portArgs = new PortArgs()
+                {
+                    TypeName = OpName,
+                    WriteWay = WriteWay.Move,
+                    Distance = distance
+                };
+                if (data.Task.TaskType== "清洗")
+                {
+                    res = res && DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    Thread.Sleep((int)(distance / 20000 * 600));
+                }
+                if (data.Task.TaskType == "润洗")
+                {
+                    portArgs.Distance *= -1;
+                    res = res && DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    Thread.Sleep((int)(distance / 20000 * 600));
+
+                    portArgs.Distance *= -11/10;
+                    res = res && DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    Thread.Sleep((int)(distance / 20000 * 700));
+                }
+                string liquidname = "FAS";
+                if (data.Task.SampleConcentration == "High")
+                {
+                    liquidname = "FAS2";
+                }
+                LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(liquidname)).First();
+                liquid1.Total -= value; //硫酸亚铁铵(高)减去20体积
+                res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            }
+            
+            logger.LogInformation("润洗(清洗)流程完成");
+            return res;
+        }
+
+        /// <summary>
+        /// 液体滴定
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private bool SampleDropLiquid(DataEventArgs data)
+        {
+            (_concentrationType, _amountValue) = data.Task.SampleConcentration switch
+            {
+                "Low" => (WriteWay.Normotopia, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).LowValue),
+                "High" => (WriteWay.Antiposition, DataCentre.GetConfig.DropOnceAmounts.FirstOrDefault(item => item.Name.Equals("FAS")).HighValue),
+                _ => throw new ArgumentNullException(data.Task.SampleConcentration),
+            };
+            ConvertConcentration(_concentrationType);
+            var res = true;
+           
+            data.LiquidDrop.Camera.OpenCamera();
+            using (TitrationStirOperate ts = new())
+            {
+                if (ts.StartStir)
+                {
+                    Thread.Sleep(1000 * _balanceVals.BeforeWaitTime);//滴定之前等待时间
+                    var task = data.Task;
+                    AdjustCamera(task.SampleConcentration);
+                    StartDropLiquid(ref task);
+                        
+                    data.Task = task;
+                    CalculateResult(task); //滴定完成需做计算
+                }
+                else
+                {
+                    res = ts.StartStir;
+                }
+            }
+            string liquidname = "FAS";
+            if (data.Task.SampleConcentration == "High")
+            {
+                liquidname = "FAS2";
+            }
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals(liquidname)).First();
+            liquid1.Total -= data.Task.Amount; //硫酸亚铁铵(低)减去滴定体积
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            
+            
+            Messager.Send("DropEnd");
+            return res;
+        }
+
+        /// <summary>
+        /// 液体滴定
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public  bool ManualSampleDropLiquid(Dictionary<string ,string>  data1)
+        {
+            EquipmentTask task1 = new EquipmentTask();
+            var data2 = task1.GetDataArgs();
+            data2.Task.QuickTitration = Convert.ToBoolean(data1["IfFastDiding"]);
+            _graber = data2.LiquidDrop.Graber;
+            _liquid = data2.DataManager.QueryLiquid(OpName);
+            if (!_liquid.Enable)
+            {
+                var dripNozzleRes = DataCentre.GetStorageContent.Factory(DripNozzleType).Stop();
+                return dripNozzleRes;
+            }
+
+            _signalTimeLen = 10;
+            _titrationCount = 0;
+            _dataManager = data2.DataManager;
+            _balanceVals = DataCentre.GetBalanceValue;
+            _balanceVals.LowValue = Convert.ToDouble(data1["YuZhiValue"]);
+            _balanceVals.HighValue = Convert.ToDouble(data1["YuZhiValue"]);
+            _balanceVals.MaxVolume = Convert.ToDouble(data1["MaxTiJiValue"]);
+            _balanceVals.AfterWaitTime = Convert.ToInt32(data1["CheckTimeValue"]);
+            _balanceVals.QuickTitrationValue = Convert.ToDouble(data1["FastDidingValue"]);
+
+            data2.Task.SampleConcentration = data1["NongDuValue"] switch
+            {
+                "低" =>"Low",
+                "高" =>"High",
+                _ => throw new ArgumentNullException(data1["NongDuValue"]),
+            };
+            (_concentrationType, _amountValue) = data2.Task.SampleConcentration switch
+            {
+                "Low" => (WriteWay.Normotopia, Convert.ToDouble(data1["DropAmountValue"])),
+                "High" => (WriteWay.Antiposition, Convert.ToDouble(data1["DropAmountValue"])),
+                _ => throw new ArgumentNullException(data2.Task.SampleConcentration),
+            };
+            ConvertConcentration(_concentrationType);
+            var res = true;
+            data2.LiquidDrop.Camera.OpenCamera();
+            using (TitrationStirOperate ts = new())
+            {
+                if (ts.StartStir)
+                {
+                    Thread.Sleep(1000 * Convert.ToInt32( data1["DengDaiTimeValue"] ));//滴定之前等待时间
+                    var task = data2.Task;
+                    AdjustCamera(task.SampleConcentration);
+                    StartDropLiquid(ref task);
+                    data2.Task = task;
+                    CalculateResult(task); //滴定完成需做计算
+                }
+                else
+                {
+                    res = ts.StartStir;
+                }
+            }
+            LiquidTotal liquid1 = DataCentre._dataManager.Query<LiquidTotal>().Where(it => it.LiquidName.Equals("FAS")).First();
+            liquid1.Total -= data2.Task.Amount; //硫酸亚铁铵(低)减去滴定体积
+            res = res && DataCentre._dataManager.Update<LiquidTotal>(liquid1) > 0;
+            Messager.Send("DropEnd");
+            Messager.Send("DropEndWindows");
+            return res;
+        }
+        /// <summary>
+        /// 滴定开始
+        /// </summary>
+        private void StartDropLiquid(ref EquipmentTask task)
+        {
+            try
+            {
+                var jumpCount = 0; //突跃次数
+                var slowSpeedCount = 0; //减速次数
+                var isRunning = true;
+                var balanceVal = GetBalanceVal(task);
+                var currSignals = new List<ColorDataPoint>(); // 信号队列
+                for (var i = 0; i < 7; i++)
+                {
+                    currSignals.Add(_graber.GrabSignal(500)); // 采集7段信号作为背景
+                }
+                PortArgs portArgs = new()
+                {
+                    TypeName = OpName,
+                    WriteWay = WriteWay.Move
+                };
+                //快速滴定
+                if (task.QuickTitration)
+                {
+                    var quickValue = Convert.ToInt32(Math.Round(_balanceVals.QuickTitrationValue / _amountValue * 20000));
+                    portArgs.Distance = quickValue;
+                    var res = true;
+                    do
+                    {
+                        res = DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                        if (res)
+                        {
+                            task.Amount += _balanceVals.QuickTitrationValue;
+                        }
+                        logger.LogInformation($"快速滴定一次性写入{quickValue},写入{(res ? "成功" : "失败")}");
+                    } while (!res);
+                    Thread.Sleep(5 * 1000);
+                    logger.LogInformation("等待五秒");
+                }
+                while (true)
+                {
+                    if (!isRunning) break;
+                    if (task.Amount >= _balanceVals.MaxVolume) break;
+                    #region 滴液
+                    portArgs.Distance = 20000;
+                    var res = DataCentre.GetClient.Factory(OpName).Write(portArgs);
+                    if (!res)
+                    {
+                        logger.LogInformation("写入失败,等待两秒后再次写入");
+                        Thread.Sleep(2000);
+                        continue;
+                    }
+                    _titrationCount += 1;
+                    task.Amount += _amountValue;
+
+                    #endregion
+
+                    #region 摄像头采集信号
+
+                    if (_titrationCount.Equals(currSignals.Count - 7))
+                    {
+                        currSignals[currSignals.Count - 1] = _graber.GrabSignal(_signalTimeLen);
+                    }
+                    else
+                    {
+                        currSignals.Add(_graber.GrabSignal(_signalTimeLen));
+                    }
+
+                    #endregion
+
+                    #region 颜色信号判断
+
+                    if (currSignals.Count >= 7) // 信号判断
+                    {
+                        var currpt = currSignals.Count - 7; // 计算起点
+                        var subB = (currSignals[currpt + 6].GetBSV() - (currSignals[currpt + 0].GetBSV() +
+                            currSignals[currpt + 1].GetBSV()) / 2) * 100 / currSignals[currpt + 6].GetBSV();
+                        logger.LogInformation($"当前第{_titrationCount}滴,体积为:{ task.Amount},滴定阈值获取为{subB}");
+                        Dictionary<string, double> dropValueArg = new()
+                        {
+                            { "Count", _titrationCount },
+                            { "Amount", task.Amount },
+                            { "BlueValue", subB }
+                        };
+                        Messager.Send("DropValue", dropValueArg);
+                        if (subB <= balanceVal)
+                        {
+                            jumpCount++;
+                            var swten = new Stopwatch();
+                            swten.Start();
+                            var retreatCount = 1; //后退次数
+                            while (true) // 继续采集信号,监测是否褪色
+                            {
+                                currSignals[currpt + 6] = _graber.GrabSignal(_signalTimeLen);
+                                subB = (currSignals[currpt + 6].GetBSV() -
+                                        (currSignals[currpt + 0].GetBSV() + currSignals[currpt + 1].GetBSV()) / 2) *
+                                    100 / currSignals[currpt + 6].GetBSV();
+                                logger.LogInformation($"检测已到达设定阈值,观察是否褪色,观察阈值为{subB}");
+                                if (subB <= balanceVal * 0.7)
+                                {
+                                    retreatCount++;
+                                    if (retreatCount > _balanceVals.AfterWaitTime)
+                                    {
+                                        break;
+                                    }
+                                    isRunning = false;
+                                    Thread.Sleep(1000);
+                                    continue; // 未褪色,继续监测
+                                }
+                                isRunning = true; //当检测到的信号值 大于阈值时,则滴定未结束,需要继续滴定
+                                if (retreatCount > 3)
+                                {
+                                    slowSpeedCount = 10; //如果回退超过三次判定,则给定慢速10次
+                                }
+
+                                break;
+                            }
+
+                            swten.Stop();
+                        }
+                        else
+                        {
+                            jumpCount = jumpCount > 0 ? jumpCount - 1 : 0;
+                            slowSpeedCount = slowSpeedCount > 0 ? slowSpeedCount - 1 : slowSpeedCount;
+                        }
+                    }
+
+                    #endregion
+
+                    Thread.Sleep((jumpCount <= 1 && slowSpeedCount <= 1) ? 800 : 3000);
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 获取液体滴定阈值
+        /// </summary>
+        private double GetBalanceVal(EquipmentTask task)
+        {
+            var balanceVal = task.SampleConcentration switch
+            {
+                "Low" => _balanceVals.LowValue * -1,
+                "High" => _balanceVals.HighValue * -1,
+                _ => throw new ArgumentNullException(task.SampleConcentration)
+            };
+            return balanceVal;
+        }
+
+        /// <summary>
+        /// 摄像头调整
+        /// </summary>
+        private void AdjustCamera(string level)
+        {
+            _graber.IsNoFiredEvent = true; // 调节曝光参数
+            if (TitrationSettings.SensorPara.UseAutoUpdateRoiPos)
+            {
+                _graber.UpdateRoiPos();
+            }
+            switch (level)
+            {
+                case "Low":
+                    _graber.PresetLightParam();
+                    break;
+                case "High":
+                    _graber.WebCam.SetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, -3, AForge.Video.DirectShow.CameraControlFlags.Manual);
+                    Thread.Sleep(2000);
+                    _graber.WebCam.GetProperties(AForge.Video.DirectShow.CameraControlProperty.Exposure, out var value, out var flags);
+                    logger.LogInformation($"高浓度设置了曝光值 ---> -2,当前曝光值为:{value}");
+                    break;
+            }
+            _graber.IsNoFiredEvent = false;
+        }
+
+
+        /// <summary>
+        /// 切换高低浓度
+        /// </summary>
+        /// <param name="way"></param>
+        private bool ConvertConcentration(WriteWay way)
+        {
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = way
+            };
+            return DataCentre.GetClient.Factory(OpName).Write(switchArg);
+        }
+        private static bool ConvertConcentrationM(WriteWay way)
+        {
+            PortArgs switchArg = new()
+            {
+                TypeName = OpName,
+                WriteWay = way
+            };
+            return DataCentre.GetClient.Factory(OpName).Write(switchArg);
+        }
+
+        /// <summary>
+        /// 计算结果
+        /// </summary>
+        private bool CalculateResult(EquipmentTask task)
+        {
+            try
+            {
+                task.Result = task.TaskType switch
+                {
+                    "水样" => GetSampleResult(task),
+                    "空白" => 0,
+                    "标定" => 0,
+                    _ => throw new ArgumentNullException(task.TaskType),
+                };
+                task.Result = Math.Round(task.Result, 2);
+                //这里是要在界面显示的样子
+                
+                task.Result= task.Result < 0 ? 0 : task.Result;
+                task.Udf1 = task.SampleConcentration.Equals("Low") ? 0 < task.Result && task.Result < 4 ? "ND" : task.Result.ToString() : task.Result.ToString();
+                //task.Udf1 = task.Result.ToString();
+                task.Amount = Convert.ToDouble(Math.Round((decimal)task.Amount, 2));
+                _dataManager.Update(task);
+                var resultArg = new Dictionary<string, string>
+                {
+                    {"LocalName", task.OriginLocalName},
+                    {"Volume",  Math.Round((decimal)task.Amount, 2).ToString()},
+                    {"Result", task.Udf1}
+                };
+                //通知更新
+                Messager.Send("SampleResult", resultArg);
+                return true;
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 获取水样的结果
+        /// </summary>
+        /// <param name="task"></param>
+        /// <returns></returns>
+        /// 计算公式 :ρ=((V * ( V0 - V1 ) * 8000 )/ V2 )* f
+        public double GetSampleResult(EquipmentTask task)
+        {
+            var blankTasks = _dataManager.GetTaskByWaveKey(task.WaveKey, task.SampleConcentration, "空白") ?? _dataManager.GetLastOtherTaskByWaveKey("空白", task.SampleConcentration);
+            SampleCalculate.tempCount = 0;
+            blankTasks.GeBlankTaskResult(_balanceVals.MaxVolume, out var noneValue);
+            var bdTasks = _dataManager.GetTaskByWaveKey(task.WaveKey, task.SampleConcentration, "标定") ?? _dataManager.GetLastOtherTaskByWaveKey("标定", task.SampleConcentration);
+            bdTasks.GetBDTaskResult(_balanceVals.MaxVolume, task.SampleConcentration, out var bdValue);
+            //var bdValue = 0.0;
+            var Taskvolumn = Math.Round(task.Amount, 2);
+            //计算参数 C:硫酸亚铁铵浓度
+            double avgValue = bdValue * (noneValue - Taskvolumn) * 8000 / 10 * task.GetSampleMultiple;
+            return avgValue;
+        }
+
+#if false
+        /// <summary>
+        /// 获取空白样的结果
+        /// </summary>
+        /// <param name="noneTasks"></param>
+        /// <param name="noneValue"></param>
+        private void GetNoneTaskResult(List<EquipmentTask> noneTasks, out double noneValue)
+        {
+            if (noneTasks?.Count > 1)
+            {
+                var noneMaxValue = noneTasks.Max(item => item.Amount);
+                var noneMinValue = noneTasks.Min(item => item.Amount);
+                if (noneMaxValue - noneMinValue <= 0.5)
+                {
+                    noneValue = noneTasks.Average(item => item.Amount);
+                    return;
+                }
+
+                if (noneMaxValue >= _balanceVals.MaxVolume)
+                {
+                    GetNoneTaskResult(noneTasks.Where(item => item.Amount != noneMaxValue).ToList(), out noneValue);
+                }
+                else
+                {
+                    noneValue = noneMaxValue;
+                }
+            }
+            else
+            {
+                var first = noneTasks?.FirstOrDefault();
+                noneValue = first?.Amount ?? 0;
+            }
+        }
+#endif
+    }
+}

+ 27 - 0
SHJX.Service.Control/Route/RouteController/TitrationStirOperate.cs

@@ -0,0 +1,27 @@
+using System;
+using SHJX.Service.Model.Control;
+using SHJX.Service.ServerClient;
+
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 搅拌
+    /// </summary>
+    public class TitrationStirOperate : IDisposable
+    {
+        private const string OpName = "TitrationStir";
+        /// <summary>
+        /// 开始搅拌
+        /// </summary>
+        public bool StartStir { get; set; }
+        public TitrationStirOperate()
+        {
+            StartStir = DataCentre.GetStorageContent.Factory(OpName).Start();
+        }
+
+        public void Dispose()
+        {
+            DataCentre.GetStorageContent.Factory(OpName).Stop();
+        }
+    }
+}

+ 27 - 0
SHJX.Service.Control/Route/RouteController/TitrationStirOperateHigh.cs

@@ -0,0 +1,27 @@
+using System;
+using SHJX.Service.Model.Control;
+using SHJX.Service.ServerClient;
+
+namespace SHJX.Service.Control.Route.RouteController
+{
+    /// <summary>
+    /// 搅拌
+    /// </summary>
+    public class TitrationStirOperateHigh : IDisposable
+    {
+        private const string OpName = "TitrationStir2";
+        /// <summary>
+        /// 开始搅拌
+        /// </summary>
+        public bool StartStir { get; set; }
+        public TitrationStirOperateHigh()
+        {
+            StartStir = DataCentre.GetStorageContent.Factory(OpName).Start();
+        }
+
+        public void Dispose()
+        {
+            DataCentre.GetStorageContent.Factory(OpName).Stop();
+        }
+    }
+}

+ 24 - 0
SHJX.Service.Control/Route/RouteFlow/CaFlow.cs

@@ -0,0 +1,24 @@
+using SHJX.Service.Common.Extend;
+using SHJX.Service.Control.Modules;
+using SHJX.Service.Control.Interface;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 升降台
+    /// </summary>
+    public class CaFlow : TaskFlowImp
+    {
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.In("CA_Down", "CA_Rise"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

+ 23 - 0
SHJX.Service.Control/Route/RouteFlow/CalibrationCoolingFlow.cs

@@ -0,0 +1,23 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 标定冷却
+    /// </summary>
+    public class CalibrationCoolingFlow : TaskFlowImp
+    {
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.Equals("Calibration_Cooling"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

+ 23 - 0
SHJX.Service.Control/Route/RouteFlow/DissolveAddLiquidFlow.cs

@@ -0,0 +1,23 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 消解位加液
+    /// </summary>
+    public class DissolveAddLiquidFlow : TaskFlowImp
+    {
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.Equals("Clear_AL1"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

+ 23 - 0
SHJX.Service.Control/Route/RouteFlow/DissolveAddWaterFlow.cs

@@ -0,0 +1,23 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 消解位加水
+    /// </summary>
+    public class DissolveAddWaterFlow : TaskFlowImp
+    {
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.Contains("Clear_AW"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

+ 27 - 0
SHJX.Service.Control/Route/RouteFlow/DissolveCoolingFlow.cs

@@ -0,0 +1,27 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 消解位冷却
+    /// </summary>
+    public class DissolveCoolingFlow : TaskFlowImp
+    {
+        /// <summary>
+        /// 操作
+        /// </summary>
+        /// <param name="data"></param>
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.Equals("Clear_Cooling"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

+ 24 - 0
SHJX.Service.Control/Route/RouteFlow/DissolveMoveFlow.cs

@@ -0,0 +1,24 @@
+using SHJX.Service.Common.Extend;
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 消解位
+    /// </summary>
+    public class DissolveMoveFlow : TaskFlowImp
+    {
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.In("Clear_In", "Clear_Out"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

+ 23 - 0
SHJX.Service.Control/Route/RouteFlow/LiquidHeatingFlow.cs

@@ -0,0 +1,23 @@
+using SHJX.Service.Control.Interface;
+using SHJX.Service.Control.Modules;
+
+namespace SHJX.Service.Control.Route.RouteFlow
+{
+    /// <summary>
+    /// 液体加热
+    /// </summary>
+    public class LiquidHeatingFlow : TaskFlowImp
+    {
+        public override void OperateHandle(DataEventArgs data)
+        {
+            if (data.Task.RouteStep.Equals("Liquid_Heating"))
+            {
+                data.Proxy.InvokeProxyMethod(ProxyName, data);
+            }
+            else
+            {
+                NextFlow?.OperateHandle(data);
+            }
+        }
+    }
+}

Деякі файли не було показано, через те що забагато файлів було змінено