Преглед изворни кода

1.统一生产端口
2.优化IHD SDK调用
3.大屏代码优化

wangxiaofei пре 1 недеља
родитељ
комит
24c51847ce

+ 1 - 1
taphole-admin/src/main/resources/application-prod.yml

@@ -9,7 +9,7 @@ like:
 
 # 服务配置
 server:
-  port: 8080
+  port: 54321
   servlet:
     context-path: /
 # 框架配置

+ 1 - 1
taphole-iron/src/main/java/com/sckj/iron/constant/TaskNameConstants.java

@@ -27,6 +27,6 @@ public class TaskNameConstants {
     //L1订阅
     public static final String TASKNAME_OPCDASUBSCRIBE = "opcdasubscribe";
 
-    public static final String TASKNAME_CLOSETIME = "closetime_suppose";
+    public static final String TASKNAME_CLOSURE_PREDICT = "closure_predict";
 
 }

+ 13 - 0
taphole-iron/src/main/java/com/sckj/iron/dto/ClosurePredictInfo.java

@@ -2,11 +2,23 @@ package com.sckj.iron.dto;
 
 import lombok.Data;
 
+/***
+ * {
+ *     "predMin":170,
+ *     "predMax":210,
+ *     "incrMinute":1,
+ *     "decrMinute":1,
+ *     "interval":1
+ * }
+ * @desc 堵口预测参数
+ */
 @Data
 public class ClosurePredictInfo {
 
+    //预测最小值
     private int predMin;
 
+    //预测最大值
     private int predMax;
 
     //增加的分钟:
@@ -15,5 +27,6 @@ public class ClosurePredictInfo {
     //减少的分钟
     private int decrMinute;
 
+    //每次计时间隔
     private int interval;
 }

+ 9 - 0
taphole-iron/src/main/java/com/sckj/iron/dto/RealtimeData.java

@@ -29,4 +29,13 @@ public class RealtimeData {
     @ApiModelProperty("时间")
     private String time;
 
+    @Override
+    public String toString() {
+        return "RealtimeData{" +
+                "value=" + value +
+                ", unit='" + unit + '\'' +
+                ", desc='" + desc + '\'' +
+                ", time='" + time + '\'' +
+                '}';
+    }
 }

+ 364 - 295
taphole-iron/src/main/java/com/sckj/iron/socketio/DeviceEventListener.java

@@ -157,13 +157,13 @@ public class DeviceEventListener extends EventListener { //
 
     //铁口出铁状态
     //1 出铁中    0 出铁结束
-    private AtomicDouble ironLoading1 = new AtomicDouble(0);
+    private AtomicDouble ironLoading1 = new AtomicDouble(-1);
     //
-    private AtomicDouble ironLoading2 = new AtomicDouble(0);
+    private AtomicDouble ironLoading2 = new AtomicDouble(-1);
     //
-    private AtomicDouble ironLoading3 = new AtomicDouble(0);
+    private AtomicDouble ironLoading3 = new AtomicDouble(-1);
     //
-    private AtomicDouble ironLoading4 = new AtomicDouble(0);
+    private AtomicDouble ironLoading4 = new AtomicDouble(-1);
 
     //出铁步骤
     private List<IronStepVO> mSteps;
@@ -218,6 +218,12 @@ public class DeviceEventListener extends EventListener { //
     //实时出铁总重量/总流量
     private BigDecimal mTotalWeight = BigDecimal.ZERO;
 
+    // 分别记录两个鱼雷罐车的最大值和当前递增值
+    private BigDecimal ironWeight11Max = BigDecimal.ZERO;
+    private BigDecimal ironWeight12Max = BigDecimal.ZERO;
+    private BigDecimal ironWeight11Current = BigDecimal.ZERO;
+    private BigDecimal ironWeight12Current = BigDecimal.ZERO;
+
     //出铁耗时(单位:秒)
     private AtomicInteger mSecondsElapsed = new AtomicInteger(0);
 
@@ -225,7 +231,7 @@ public class DeviceEventListener extends EventListener { //
     private static final int MAX_REDIS_COUNT = 50;
 
     //上次出铁量
-    private OPCData mIronOPCData;
+//    private OPCData mIronOPCData;
 
     //实时出铁数据
     private TIronData mTIronData;
@@ -255,8 +261,13 @@ public class DeviceEventListener extends EventListener { //
             if ("1".equals(scheduleOpcdasubscribe.getStatus())) {
                 //程序启动后隔断时间启动订阅
                 scheduledTaskManager.addTask(scheduleOpcdasubscribe.getName(), scheduleOpcdasubscribe.getDelay(), scheduleOpcdasubscribe.getPeriod(), TimeUnit.SECONDS, () -> {
-                    log.info("opcdaService subscribe available");
-                    opcuaService.subscribeAvailable();
+                    if ("prod".equals(activeProfiles)) {
+                        log.info("hdService subscribe available");
+                        hdService.subscribeAvailable();
+                    } else {
+                        log.info("opcdaService subscribe available");
+                        opcuaService.subscribeAvailable();
+                    }
                     scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_OPCDASUBSCRIBE);
                 });
             }
@@ -280,8 +291,6 @@ public class DeviceEventListener extends EventListener { //
 
     private TIronModel modelClosurePredict;
 
-    private ClosurePredictInfo mClosurePredictInfo;
-
 
     /***
      * 更新模型
@@ -300,31 +309,6 @@ public class DeviceEventListener extends EventListener { //
         modelTappingWarn = modelMap.get(ModelConstants.tapping_warn);
         modelClosurePredict = modelMap.get(ModelConstants.closure_predict);
 
-        if (null != modelClosurePredict && "1".equals(modelClosurePredict.getStatus())) {
-            String modelExpression = modelClosurePredict.getModelExpression();
-            if (null != modelExpression) {
-                mClosurePredictInfo = new Gson().fromJson(modelExpression, ClosurePredictInfo.class);
-                if (null != mClosurePredictInfo) {
-                    totalHitTime = new Random().nextInt(mClosurePredictInfo.getPredMax() - mClosurePredictInfo.getPredMin() + 1) + mClosurePredictInfo.getPredMin();
-                    scheduledTaskManager.cancelTask(modelClosurePredict.getModelName());
-                    scheduledTaskManager.addTask(modelClosurePredict.getModelName(), 0, mClosurePredictInfo.getInterval(), TimeUnit.SECONDS, () -> {
-                        if (mMaxSpeed != 0 && ironLoading1.get() > 0) {
-                            if (this.mLastSpeed == 0) {
-                                this.mLastSpeed = mMaxSpeed;
-                            } else if (mMaxSpeed > this.mLastSpeed) {
-                                mDiffCloseTime.set(mDiffCloseTime.get() + mClosurePredictInfo.getDecrMinute());
-                                this.mLastSpeed = mMaxSpeed;
-                            } else if (mMaxSpeed < this.mLastSpeed) {
-                                mDiffCloseTime.set(mDiffCloseTime.get() - mClosurePredictInfo.getIncrMinute());
-                                this.mLastSpeed = mMaxSpeed;
-                            }
-                            getCloseTime();
-                        }
-                    });
-                }
-            }
-        }
-
     }
 
     private void getAudios() {
@@ -383,20 +367,29 @@ public class DeviceEventListener extends EventListener { //
         }
     }
 
+    //堵口预警
     private TIronSchedule scheduleClosureWarn;
+    //开口预警
     private TIronSchedule scheduleOpenWarn;
+    //出铁预警
     private TIronSchedule scheduleTappingWarn;
+    //出铁超时预警
     private TIronSchedule scheduleTappingTimeoutWarn;
+    //出铁后多长时间打泥量计算
     private TIronSchedule scheduleHitMud;
+    //出铁计时开始
     private TIronSchedule scheduleTappingConsttime;
+    //出铁诊断
     private TIronSchedule scheduleTappingTest;
+    //程序启动后多长时间开始订阅
     private TIronSchedule scheduleOpcdasubscribe;
+    //堵口预测
+    private TIronSchedule scheduleClosurePredict;
 
     /***
      * 定时任务
      */
     private void getSchedules() {
-
         Map<String, TIronSchedule> modelMap = iTIronScheduleService.lambdaQuery().list().stream().collect(Collectors.toMap(
                 TIronSchedule::getName, // 键映射函数
                 schedule -> schedule, // 值映射函数
@@ -410,6 +403,7 @@ public class DeviceEventListener extends EventListener { //
         scheduleTappingConsttime = modelMap.get(TaskNameConstants.TASKNAME_TAPPING_CONSTTIME);
         scheduleTappingTest = modelMap.get(TaskNameConstants.TASKNAME_TAPPING_TEST);
         scheduleOpcdasubscribe = modelMap.get(TaskNameConstants.TASKNAME_OPCDASUBSCRIBE);
+        scheduleClosurePredict = modelMap.get(TaskNameConstants.TASKNAME_CLOSURE_PREDICT);
     }
 
 
@@ -464,7 +458,7 @@ public class DeviceEventListener extends EventListener { //
     }
 
     /***
-     * 显示“铁量差在合理范围内”
+     * 显示"铁量差在合理范围内"
      * @param latest2DataList
      */
     private void getTheoryWeight(List<TL2Data> latest2DataList) {
@@ -544,164 +538,198 @@ public class DeviceEventListener extends EventListener { //
 
     }
 
-    private int totalHitTime;
+    //总堵口时间
+    private int mTotalCloseTime;
+
     //代表要减去的时间,非剩余时间
     private AtomicInteger mDiffCloseTime = new AtomicInteger(0);
 
     private void getCloseTime() {
         RealtimeData realtimeData = new RealtimeData();
-        realtimeData.setValue(Math.max(totalHitTime - getIronElapsedMinute() - mDiffCloseTime.get(), 0));
+        realtimeData.setValue(Math.max(mTotalCloseTime - getIronElapsedMinute() - mDiffCloseTime.get(), 0));
         realtimeData.setDesc("距离堵口预计还剩");
         realtimeData.setUnit("分钟");
         PushData.send2CloseTime(realtimeData);
     }
 
-    //1号铁口正在出铁的操作项目
+    /**
+     * 1号铁口正在出铁的操作项目(由 0-> 1 表明1号铁口开始出铁)
+     * 处理出铁开始事件
+     * 1. 关闭定时任务 -> 出铁诊断(执行一次)、出铁预警、开口预警
+     * 2. 开启定时任务 -> 出铁超时报警、堵口预警、打泥量选择计算、出铁计时(按秒计时)
+     * 3. 清空出铁总量
+     */
     private void taphole1Start() {
-        //关闭定时任务:出铁预警、开口预警
-        //开启定时任务:出铁超时报警、堵口预警、打泥量选择计算、
-
         mTIronData = new TIronData();
         mTIronData.setId(ToolUtils.makeUUID());
         mTIronData.setIronStarttime(LocalDateUtils.formatDate(new Date()));
         ironDataService.saveOrUpdate(mTIronData);
 
-        //清空出铁总量、出铁计时
-        mTotalWeight = BigDecimal.ZERO;
-        mSecondsElapsed.set(0);
-        //生成 170 到 210 的随机数
-
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_TAPPING_WARN);
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_TAPPING_TIMEOUT_WARN);
+        synchronized (scheduledTaskManager) {
+            //清空出铁总量
+            mTotalWeight = BigDecimal.ZERO;
+            //重置出铁计时
+            mSecondsElapsed.set(0);
+
+            scheduledTaskManager.cancelTask(scheduleClosureWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleOpenWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingTimeoutWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleHitMud.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingConsttime.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingTest.getName());
+            scheduledTaskManager.cancelTask(scheduleClosurePredict.getName());
 
-        if ("1".equals(scheduleTappingConsttime.getStatus())) {
             //出铁计时
-            scheduledTaskManager.addTask(scheduleTappingConsttime.getName(), scheduleTappingConsttime.getDelay(), scheduleTappingConsttime.getPeriod(), TimeUnit.SECONDS, () -> {
-                getIronTime();
-                mSecondsElapsed.incrementAndGet();
-            });
-
-        }
+            if ("1".equals(scheduleTappingConsttime.getStatus())) {
+                scheduledTaskManager.addTask(scheduleTappingConsttime.getName(), scheduleTappingConsttime.getDelay(), scheduleTappingConsttime.getPeriod(), TimeUnit.SECONDS, () -> {
+                    getIronTime();
+                    mSecondsElapsed.incrementAndGet();
+                });
+            }
 
-        //模型四:堵口模型
-        if ("1".equals(scheduleClosureWarn.getStatus())) {
-            //堵口预警
-            scheduledTaskManager.addTask(scheduleClosureWarn.getName(), scheduleClosureWarn.getDelay(), scheduleClosureWarn.getPeriod(), TimeUnit.SECONDS, () -> {
+            //模型四:堵口模型
+            if ("1".equals(scheduleClosureWarn.getStatus())) {
                 //堵口预警
-                String modelExpression1 = modelClosureWarn1.getModelExpression();
-
-                Expression expression1 = mParser.parseExpression(modelExpression1);
-
-                StandardEvaluationContext context = new StandardEvaluationContext();
-                // 设置占位符对应的值
-                boolean result1 = expression1.getValue(context, Boolean.class);
-
-                log.info("1号车受铁速度:{}", speed1.get());
-                log.info("2号车受铁速度:{}", speed2.get());
-                log.info("标准受铁速度:{},", STANDARD_SPEED.get());
-                log.info("1号铁口出铁状态:{}", ironLoading1.get());
-                log.info("2号铁口出铁状态:{}", ironLoading2.get());
-                log.info("3号铁口出铁状态:{}", ironLoading3.get());
-                log.info("4号铁口出铁状态:{}", ironLoading4.get());
-
-                if (result1) {
-                    //流速过大可能是由于铁口深度不足或发生跑大流问题,则提示将当前铁口堵口
-                    PushData.send2Warn(WarnData.warnClose("流速过快,请将当前铁口堵口", closureAlarmUrl));
-                    taskExecutor.submit(() -> {
-                        exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("流速过快,请将当前铁口堵口").build());
-                    });
-                    return;
-                }
-
-                String modelExpression2 = modelClosureWarn2.getModelExpression();
-                Expression expression2 = mParser.parseExpression(modelExpression2);
-                boolean result2 = expression2.getValue(context, Boolean.class);
-                if (result2) {
-                    //若流速过小,但其它铁口正在出铁,则提示将当前铁口堵口
-                    PushData.send2Warn(WarnData.warnClose("流速过小,请将当前铁口堵口", closureAlarmUrl));
-                    taskExecutor.submit(() -> {
-                        exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("流速过小,请将当前铁口堵口").build());
-                    });
-                    return;
-                }
+                scheduledTaskManager.addTask(scheduleClosureWarn.getName(), scheduleClosureWarn.getDelay(), scheduleClosureWarn.getPeriod(), TimeUnit.SECONDS, () -> {
+                    //堵口预警
+                    String modelExpression1 = modelClosureWarn1.getModelExpression();
 
-                String modelExpression3 = modelClosureWarn3.getModelExpression();
-                Expression expression3 = mParser.parseExpression(modelExpression3);
-                boolean result3 = expression3.getValue(context, Boolean.class);
+                    Expression expression1 = mParser.parseExpression(modelExpression1);
 
-                if (result3) {
-                    PushData.send2Warn(WarnData.warnClose("流速过小且其他铁口均未出铁,请先将其它铁口打开,再进行堵口", closureAlarmUrl));
-                    taskExecutor.submit(() -> {
-                        exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("流速过小且其他铁口均未出铁,请先将其它铁口打开,再进行堵口").build());
-                    });
-                }
-
-
-            });
-        }
+                    StandardEvaluationContext context = new StandardEvaluationContext();
+                    // 设置占位符对应的值
+                    boolean result1 = expression1.getValue(context, Boolean.class);
+
+                    log.info("1号车受铁速度:{}", speed1.get());
+                    log.info("2号车受铁速度:{}", speed2.get());
+                    log.info("标准受铁速度:{},", STANDARD_SPEED.get());
+                    log.info("1号铁口出铁状态:{}", ironLoading1.get());
+                    log.info("2号铁口出铁状态:{}", ironLoading2.get());
+                    log.info("3号铁口出铁状态:{}", ironLoading3.get());
+                    log.info("4号铁口出铁状态:{}", ironLoading4.get());
+
+                    if (result1) {
+                        //流速过大可能是由于铁口深度不足或发生跑大流问题,则提示将当前铁口堵口
+                        PushData.send2Warn(WarnData.warnClose("流速过快,请将当前铁口堵口", closureAlarmUrl));
+                        taskExecutor.submit(() -> {
+                            exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("流速过快,请将当前铁口堵口").build());
+                        });
+                        return;
+                    }
 
+                    String modelExpression2 = modelClosureWarn2.getModelExpression();
+                    Expression expression2 = mParser.parseExpression(modelExpression2);
+                    boolean result2 = expression2.getValue(context, Boolean.class);
+                    if (result2) {
+                        //若流速过小,但其它铁口正在出铁,则提示将当前铁口堵口
+                        PushData.send2Warn(WarnData.warnClose("流速过小,请将当前铁口堵口", closureAlarmUrl));
+                        taskExecutor.submit(() -> {
+                            exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("流速过小,请将当前铁口堵口").build());
+                        });
+                        return;
+                    }
 
-        //模型二:打泥量选择模型
-        if ("1".equals(scheduleHitMud.getStatus())) {
-            //xxx分钟出铁后开始计算打泥量,通过打泥量公式
-            //打泥量公式关联因素:铁口深度、钻杆直径、调用打泥量模型,计算预计使用多少打泥量进行堵口
-            scheduledTaskManager.addTask(scheduleHitMud.getName(), scheduleHitMud.getDelay(), scheduleHitMud.getPeriod(), TimeUnit.SECONDS, () -> {
-                log.info("打泥量预计:{}", modelHitMud);
-                TL2Data tappingData = tl2DataService.getLatestData();
-                if (ObjectUtils.isNotEmpty(tappingData) && ObjectUtils.isNotEmpty(modelHitMud)) {
-                    log.info("开口深度openDepth(mm):{}", tappingData.getOpenDepth());
-                    log.info("Tap对应铁水估计铁量rtIronWeight(t): {}", mTotalWeight.doubleValue());
-                    log.info("出铁时间rtIronCosttime(min): {}", getIronElapsedMinute());
-                    log.info("平均流速rtIronSpeed(t/s): {}", speed1.get() > speed2.get() ? speed1.get() : speed2.get());
+                    String modelExpression3 = modelClosureWarn3.getModelExpression();
+                    Expression expression3 = mParser.parseExpression(modelExpression3);
+                    boolean result3 = expression3.getValue(context, Boolean.class);
 
-                    try {
-                        String modelExpression = modelHitMud.getModelExpression();
+                    if (result3) {
+                        PushData.send2Warn(WarnData.warnClose("流速过小且其他铁口均未出铁,请先将其它铁口打开,再进行堵口", closureAlarmUrl));
+                        taskExecutor.submit(() -> {
+                            exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("流速过小且其他铁口均未出铁,请先将其它铁口打开,再进行堵口").build());
+                        });
+                    }
+                });
+            }
 
-                        Expression expression = mParser.parseExpression(modelExpression);
-                        // 设置占位符对应的值
-                        mContext.setVariable(ExpressionConstants.openDepth, tappingData.getOpenDepth());
-                        mContext.setVariable(ExpressionConstants.rtIronWeight, mTotalWeight.doubleValue());
-                        mContext.setVariable(ExpressionConstants.rtIronCosttime, getIronElapsedMinute());
-                        //计算打泥量
-                        int result = (int) ((double) expression.getValue(mContext));
-                        // 使用 DecimalFormat 保留两位小数
-//                    DecimalFormat decimalFormat = new DecimalFormat("#.00");
-//                    String formattedResult = decimalFormat.format(result);
-                        log.info("计算结果: {}", result);
-                        PushData.send2IronHitMud(result);
-                    } catch (Exception e) {
-                        e.printStackTrace();
+            //模型二:打泥量选择模型
+            if ("1".equals(scheduleHitMud.getStatus())) {
+                //xxx分钟出铁后开始计算打泥量,通过打泥量公式
+                //打泥量公式关联因素:铁口深度、钻杆直径、调用打泥量模型,计算预计使用多少打泥量进行堵口
+                scheduledTaskManager.addTask(scheduleHitMud.getName(), scheduleHitMud.getDelay(), scheduleHitMud.getPeriod(), TimeUnit.SECONDS, () -> {
+                    log.info("打泥量预计:{}", modelHitMud);
+                    TL2Data tappingData = tl2DataService.getLatestData();
+                    if (ObjectUtils.isNotEmpty(tappingData) && ObjectUtils.isNotEmpty(modelHitMud)) {
+                        log.info("开口深度openDepth(mm):{}", tappingData.getOpenDepth());
+                        log.info("Tap对应铁水估计铁量rtIronWeight(t): {}", mTotalWeight.doubleValue());
+                        log.info("出铁时间rtIronCosttime(min): {}", getIronElapsedMinute());
+                        log.info("平均流速rtIronSpeed(t/s): {}", speed1.get() > speed2.get() ? speed1.get() : speed2.get());
+
+                        try {
+                            String modelExpression = modelHitMud.getModelExpression();
+
+                            Expression expression = mParser.parseExpression(modelExpression);
+                            // 设置占位符对应的值
+                            mContext.setVariable(ExpressionConstants.openDepth, tappingData.getOpenDepth());
+                            mContext.setVariable(ExpressionConstants.rtIronWeight, mTotalWeight.doubleValue());
+                            mContext.setVariable(ExpressionConstants.rtIronCosttime, getIronElapsedMinute());
+                            //计算打泥量
+                            BigDecimal bigDecimal = new BigDecimal(expression.getValue(mContext).toString());
+                            String formattedResult = bigDecimal.toBigInteger().toString();
+                            log.info("计算结果: {}", formattedResult);
+                            PushData.send2IronHitMud(formattedResult);
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
                     }
-                }
-                scheduledTaskManager.cancelTask(scheduleHitMud.getName());
-            });
-        }
+                    scheduledTaskManager.cancelTask(scheduleHitMud.getName());
+                });
+            }
 
+            //出铁超时报警
+            if ("1".equals(scheduleTappingTimeoutWarn.getStatus())) {
+                scheduledTaskManager.addTask(scheduleTappingTimeoutWarn.getName(), scheduleTappingTimeoutWarn.getDelay(), scheduleTappingTimeoutWarn.getPeriod(), TimeUnit.SECONDS, () -> {
+                    // log.info("已出铁时间(秒):{},标准出铁时间(秒):{}", seconds, STANDARD_IRON_TIME.get());
+                    if (getIronElapsedMinute() > STANDARD_IRON_TIME.get()) {
+                        PushData.send2Warn(WarnData.warnTappingTimeout("出铁时间超时", tappingTimeoutAlramUrl));
+                        taskExecutor.submit(() -> {
+                            exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("2").exceptionDesc("出铁时间超过设定时间").build());
+                        });
+                    }
+                });
+            }
 
-        //出铁超时报警
-        if ("1".equals(scheduleTappingTimeoutWarn.getStatus())) {
-            scheduledTaskManager.addTask(scheduleTappingTimeoutWarn.getName(), scheduleTappingTimeoutWarn.getDelay(), scheduleTappingTimeoutWarn.getPeriod(), TimeUnit.SECONDS, () -> {
-                // log.info("已出铁时间(秒):{},标准出铁时间(秒):{}", seconds, STANDARD_IRON_TIME.get());
-                if (getIronElapsedMinute() > STANDARD_IRON_TIME.get()) {
-                    PushData.send2Warn(WarnData.warnTappingTimeout("出铁时间超时", tappingTimeoutAlramUrl));
-                    taskExecutor.submit(() -> {
-                        exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("2").exceptionDesc("出铁时间超过设定时间").build());
-                    });
+            if (null != modelClosurePredict && "1".equals(modelClosurePredict.getStatus())) {
+                String modelExpression = modelClosurePredict.getModelExpression();
+                if (null != modelExpression) {
+                    ClosurePredictInfo mClosurePredictInfo = new Gson().fromJson(modelExpression, ClosurePredictInfo.class);
+                    if (null != mClosurePredictInfo) {
+                        if ("1".equals(scheduleClosurePredict.getStatus())) {
+                            mTotalCloseTime = new Random().nextInt(mClosurePredictInfo.getPredMax() - mClosurePredictInfo.getPredMin() + 1) + mClosurePredictInfo.getPredMin();
+                            mLastSpeed = mMaxSpeed = 0;
+                            mDiffCloseTime.set(0);
+                            log.info("预计总时间:{}", mTotalCloseTime);
+                            scheduledTaskManager.addTask(scheduleClosurePredict.getName(), scheduleClosurePredict.getDelay(), scheduleClosurePredict.getPeriod(), TimeUnit.SECONDS, () -> {
+                                if (mMaxSpeed != 0 && ironLoading1.get() > 0) {
+                                    if (this.mLastSpeed == 0) {
+                                        this.mLastSpeed = mMaxSpeed;
+                                    } else if (mMaxSpeed > this.mLastSpeed) {
+                                        mDiffCloseTime.set(mDiffCloseTime.get() + mClosurePredictInfo.getDecrMinute());
+                                        this.mLastSpeed = mMaxSpeed;
+                                    } else if (mMaxSpeed < this.mLastSpeed) {
+                                        mDiffCloseTime.set(mDiffCloseTime.get() - mClosurePredictInfo.getIncrMinute());
+                                        this.mLastSpeed = mMaxSpeed;
+                                    }
+                                    getCloseTime();
+                                }
+                            });
+                        }
+                    }
                 }
-            });
+            }
         }
-
-        scheduledTaskManager.addTask(TaskNameConstants.TASKNAME_CLOSETIME, 0, 1 * 60, TimeUnit.SECONDS, () -> {
-            // log.info("已出铁时间(秒):{},标准出铁时间(秒):{}", seconds, STANDARD_IRON_TIME.get());
-
-        });
-
     }
 
 
-    //1号铁口结束出铁的操作项目
+    /**
+     * 1号铁口结束出铁的操作项目(由 1-> 0 表明1号铁口结束出铁)
+     * 处理出铁开始事件
+     * 1. 关闭定时任务 -> 出铁超时报警、堵口预警、打泥量选择计算、出铁计时
+     * 2. 开启定时任务 -> 出铁诊断(执行一次)、出铁预警、开口预警
+     * 3. 清空出铁总量
+     */
     private void taphole1End() {
-        //由 1-> 0 表明1号铁口结束出铁
         if (null != mTIronData) {
             mTIronData.setIronCosttime(getIronElapsedMinute());
             mTIronData.setIronWeight(mTotalWeight.doubleValue());
@@ -709,123 +737,115 @@ public class DeviceEventListener extends EventListener { //
             ironDataService.saveOrUpdate(mTIronData);
         }
 
-        mLastSpeed = mMaxSpeed = 0;
-        mDiffCloseTime.set(0);
-        if (null != mClosurePredictInfo) {
-            totalHitTime = new Random().nextInt(mClosurePredictInfo.getPredMax() - mClosurePredictInfo.getPredMin() + 1) + mClosurePredictInfo.getPredMin();
-        }
-
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_CLOSURE_WARN);
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_HIT_MUD);
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_TAPPING_CONSTTIME);
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_TAPPING_TIMEOUT_WARN);
-        scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_CLOSETIME);
+        synchronized (scheduledTaskManager) {
+            scheduledTaskManager.cancelTask(scheduleClosureWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleOpenWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingTimeoutWarn.getName());
+            scheduledTaskManager.cancelTask(scheduleHitMud.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingConsttime.getName());
+            scheduledTaskManager.cancelTask(scheduleTappingTest.getName());
+            scheduledTaskManager.cancelTask(scheduleClosurePredict.getName());
+
+            PushData.send2CostTime(new RealtimeData());
+            PushData.send2CloseTime(new RealtimeData());
+
+            //尝试重新获取一次新的铁次号码
+            getIronTimeNo();
+            mSteps = ironStepService.getTreeSteps();
 
-        PushData.send2CostTime(new RealtimeData());
-        PushData.send2CloseTime(new RealtimeData());
+            //开口
+            //PushData.send2CancelWarn(WarnData.warnOpen("", ""));
+            //堵口
+            PushData.send2CancelWarn(WarnData.warnClose("出铁结束", ""));
+            //出铁预警
+            PushData.send2CancelWarn(WarnData.warnTapping("出铁结束", ""));
+            //清空打泥量
+            PushData.send2IronHitMud("");
+
+            //模型一 : 出铁工作诊断模型
+            if ("1".equals(scheduleTappingTest.getStatus())) {
+                //获取开口耗时、出铁时间、实际出铁量、平均铁水流速、平均铁水温度等数据,进行阈值判定,诊断出铁是否正常
+                //结束后xxx秒后诊断
+                scheduledTaskManager.addTask(scheduleTappingTest.getName(), scheduleTappingTest.getDelay(), scheduleTappingTest.getPeriod(), TimeUnit.SECONDS, () -> {
+                    //堵口预警
+                    log.info("出铁结束,定时任务:{},出铁诊断", TaskNameConstants.TASKNAME_TAPPING_TEST);
+                    TL2Data fixedLatestElement = (TL2Data) RedisUtils.getFixedLatestElement(IRON_ELEMENT);
 
-        //尝试重新获取一次新的铁次号码
-        getIronTimeNo();
-        mSteps = ironStepService.getTreeSteps();
-
-
-        //开口
-//        PushData.send2CancelWarn(WarnData.warnOpen("", ""));
-        //堵口
-        PushData.send2CancelWarn(WarnData.warnClose("出铁结束", ""));
-        //出铁预警
-        PushData.send2CancelWarn(WarnData.warnTapping("出铁结束", ""));
-        //清空打泥量
-        PushData.send2IronHitMud("");
-
-//        recordAfter();
-//        recordBlock();
-
-        //模型一 : 出铁工作诊断模型
-        if ("1".equals(scheduleTappingTest.getStatus())) {
-            //获取开口耗时、出铁时间、实际出铁量、平均铁水流速、平均铁水温度等数据,进行阈值判定,诊断出铁是否正常
-            //结束后xxx秒后诊断
-            scheduledTaskManager.addTask(scheduleTappingTest.getName(), scheduleTappingTest.getDelay(), scheduleTappingTest.getPeriod(), TimeUnit.SECONDS, () -> {
-                //堵口预警
-                log.info("出铁结束,定时任务:{},出铁诊断", TaskNameConstants.TASKNAME_TAPPING_TEST);
-                TL2Data fixedLatestElement = (TL2Data) RedisUtils.getFixedLatestElement(IRON_ELEMENT);
+                    try {
+                        String modelExpression = modelTappingTest.getModelExpression();
+                        if (ObjectUtils.isNotEmpty(modelExpression)) {
+                            log.info("出铁诊断计算公式:{}", modelExpression);
+                            // 设置占位符对应的值
+                            mContext.setVariable(ExpressionConstants.rtIronCosttime, getIronElapsedMinute());
+                            mContext.setVariable(ExpressionConstants.avgIronSpeed, mTotalWeight.doubleValue() / (getIronElapsedMinute() == 0 ? 1 : getIronElapsedMinute()));
+                            // 使用 DecimalFormat 保留两位小数
+                            //DecimalFormat decimalFormat = new DecimalFormat("#.00");
+                            //String formattedResult = decimalFormat.format(result);
+                            String testResultStr = "";
+
+                            String[] split = modelExpression.split("&&");
+                            boolean ironTimeBool = mParser.parseExpression(split[0]).getValue(mContext, Boolean.class);
+
+                            if (ironTimeBool) {
+                                testResultStr += "出铁时间符合预期,";
+                            } else {
+                                testResultStr += "出铁时间不符合预期,";
+                            }
 
-                try {
-                    String modelExpression = modelTappingTest.getModelExpression();
-                    if (ObjectUtils.isNotEmpty(modelExpression)) {
-                        log.info("出铁诊断计算公式:{}", modelExpression);
-                        // 设置占位符对应的值
-                        mContext.setVariable(ExpressionConstants.rtIronCosttime, getIronElapsedMinute());
-                        mContext.setVariable(ExpressionConstants.avgIronSpeed, mTotalWeight.doubleValue() / (getIronElapsedMinute() == 0 ? 1 : getIronElapsedMinute()));
-                        // 使用 DecimalFormat 保留两位小数
-//                      DecimalFormat decimalFormat = new DecimalFormat("#.00");
-//                      String formattedResult = decimalFormat.format(result);
-                        String testResultStr = "";
-
-                        String[] split = modelExpression.split("&&");
-                        boolean ironTimeBool = mParser.parseExpression(split[0]).getValue(mContext, Boolean.class);
-
-                        if (ironTimeBool) {
-                            testResultStr += "出铁时间符合预期,";
-                        } else {
-                            testResultStr += "出铁时间不符合预期,";
-                        }
+                            boolean ironWeightBool = mParser.parseExpression(split[1]).getValue(mContext, Boolean.class);
+                            if (ironWeightBool) {
+                                testResultStr += "出铁量符合预期,";
+                            } else {
+                                testResultStr += "出铁量不符合预期,";
+                            }
 
-                        boolean ironWeightBool = mParser.parseExpression(split[1]).getValue(mContext, Boolean.class);
-                        if (ironWeightBool) {
-                            testResultStr += "出铁量符合预期,";
-                        } else {
-                            testResultStr += "出铁量不符合预期,";
-                        }
+                            boolean ironSpeedBool = mParser.parseExpression(split[2]).getValue(mContext, Boolean.class);
+                            if (ironSpeedBool) {
+                                testResultStr += "出铁流速符合预期,";
+                            } else {
+                                testResultStr += "出铁流速不符合预期,";
+                            }
 
-                        boolean ironSpeedBool = mParser.parseExpression(split[2]).getValue(mContext, Boolean.class);
-                        if (ironSpeedBool) {
-                            testResultStr += "出铁流速符合预期,";
-                        } else {
-                            testResultStr += "出铁流速不符合预期,";
+                            boolean ironTempChangeBool = mParser.parseExpression(split[3]).getValue(mContext, Boolean.class);
+                            if (ironTempChangeBool) {
+                                testResultStr += "铁水温度变化符合预期。";
+                            } else {
+                                testResultStr += "铁水温度变化不符合预期。";
+                            }
+                            TIronTest ironTest = new TIronTest();
+                            String testStatus = (ironTimeBool && ironWeightBool && ironSpeedBool && ironTempChangeBool) ? "1" : "0";
+                            ironTest.setTestStatus(testStatus);
+                            ironTest.setTestDesc(testResultStr);
+                            iTIronTestService.save(ironTest);
+                            log.info("出铁诊断结果:{}", testResultStr);
                         }
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    } finally {
+                        scheduledTaskManager.cancelTask(scheduleTappingTest.getName());
+                    }
+                });
+            }
 
-                        boolean ironTempChangeBool = mParser.parseExpression(split[3]).getValue(mContext, Boolean.class);
-                        if (ironTempChangeBool) {
-                            testResultStr += "铁水温度变化符合预期。";
+            //模型三:预警出铁模型
+            if ("1".equals(scheduleTappingWarn.getStatus())) {
+                scheduledTaskManager.addTask(scheduleTappingWarn.getName(), scheduleTappingWarn.getDelay(), scheduleTappingWarn.getPeriod(), TimeUnit.SECONDS, () -> {
+                    String modelExpression = modelTappingWarn.getModelExpression();
+                    if (ObjectUtils.isNotEmpty(modelExpression)) {
+                        Expression expression = mParser.parseExpression(modelExpression);
+                        // 设置占位符对应的值
+                        Boolean result = expression.getValue(mContext, Boolean.class);
+                        if (result) {
+                            PushData.send2Warn(WarnData.warnTapping("压差超过阈值,请出铁", tappingAlramUrl));
+                            exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("压差超过阈值,请出铁").build());
                         } else {
-                            testResultStr += "铁水温度变化不符合预期。";
+                            PushData.send2CancelWarn(WarnData.warnTapping("压差正常", ""));
                         }
-                        TIronTest ironTest = new TIronTest();
-                        String testStatus = (ironTimeBool && ironWeightBool && ironSpeedBool && ironTempChangeBool) ? "1" : "0";
-                        ironTest.setTestStatus(testStatus);
-                        ironTest.setTestDesc(testResultStr);
-                        iTIronTestService.save(ironTest);
-                        log.info("出铁诊断结果:{}", testResultStr);
                     }
-                } catch (Exception e) {
-                    e.printStackTrace();
-                } finally {
-                    scheduledTaskManager.cancelTask(scheduleTappingTest.getName());
-                }
-
-            });
-        }
-
-        //模型三:预警出铁模型
-        if ("1".equals(scheduleTappingWarn.getStatus())) {
-            scheduledTaskManager.addTask(scheduleTappingWarn.getName(), scheduleTappingWarn.getDelay(), scheduleTappingWarn.getPeriod(), TimeUnit.SECONDS, () -> {
-                String modelExpression = modelTappingWarn.getModelExpression();
-                if (ObjectUtils.isNotEmpty(modelExpression)) {
-                    Expression expression = mParser.parseExpression(modelExpression);
-                    // 设置占位符对应的值
-                    Boolean result = expression.getValue(mContext, Boolean.class);
-                    if (result) {
-                        PushData.send2Warn(WarnData.warnTapping("压差超过阈值,请出铁", tappingAlramUrl));
-                        exceptionLogService.add(TExceptionLogCreateValidate.builder().exceptionType("4").exceptionDesc("压差超过阈值,请出铁").build());
-                    } else {
-                        PushData.send2CancelWarn(WarnData.warnTapping("压差正常", ""));
-                    }
-                }
-            });
+                });
+            }
         }
-
-
     }
 
     //出铁后,记录炉次、开口时间、鱼雷罐车车号、铁口深度、铁水流速
@@ -877,24 +897,39 @@ public class DeviceEventListener extends EventListener { //
                 realtimeData.setUnit("t/s");
 
                 RealtimeData ironSpeed = (RealtimeData) mRealtimeData.get(IRON_SPEED);
-                RealtimeData[] speeds = null;
+                List<RealtimeData> speeds = null;
+
                 if (ObjectUtils.isEmpty(ironSpeed)) {
                     ironSpeed = new RealtimeData();
                     ironSpeed.setDesc("铁水流速");
-                    speeds = new RealtimeData[2];
+
+                    // 初始化包含两个默认元素的 List
+                    speeds = new ArrayList<>();
+                    speeds.add(null); // index 0
+                    speeds.add(null); // index 1
+
                     ironSpeed.setValue(speeds);
                     mRealtimeData.put(IRON_SPEED, ironSpeed);
                 } else {
-                    speeds = (RealtimeData[]) ironSpeed.getValue();
+                    speeds = (List<RealtimeData>) ironSpeed.getValue();
                 }
 
                 if (opcData.getPointName().contains(SubscribeTagConstants.TAG_CAR11(opcData.getServerType()))) {
                     realtimeData.setDesc("1号车");
-                    speeds[0] = realtimeData;
+                    if (speeds.size() > 0) {
+                        speeds.set(0, realtimeData);
+                    } else {
+                        speeds.add(realtimeData); // 如果不够长度,就添加
+                    }
                     speed1 = new AtomicDouble(Double.parseDouble(opcData.getData().toString()));
                 } else {
                     realtimeData.setDesc("2号车");
-                    speeds[1] = realtimeData;
+                    if (speeds.size() > 1) {
+                        speeds.set(1, realtimeData);
+                    } else {
+                        while (speeds.size() < 1) speeds.add(null); // 补足长度
+                        speeds.add(realtimeData);
+                    }
                     speed2 = new AtomicDouble(Double.parseDouble(opcData.getData().toString()));
                 }
                 mMaxSpeed = Math.max(speed1.get(), speed2.get());
@@ -902,8 +937,8 @@ public class DeviceEventListener extends EventListener { //
 
                 //只在两个都有数据的时候才添加
                 if (ObjectUtils.isNotEmpty(speeds)
-                        && ObjectUtils.isNotEmpty(speeds[0]) && ObjectUtils.isNotEmpty(speeds[0].getValue())
-                        && ObjectUtils.isNotEmpty(speeds[1]) && ObjectUtils.isNotEmpty(speeds[1].getValue())
+                        && ObjectUtils.isNotEmpty(speeds.get(0)) && ObjectUtils.isNotEmpty(speeds.get(0).getValue())
+                        && ObjectUtils.isNotEmpty(speeds.get(1)) && ObjectUtils.isNotEmpty(speeds.get(1).getValue())
                 ) {
                     ironSpeed.setTime(LocalDateUtils.formatDate(opcData.getServerTime()));
                 }
@@ -916,16 +951,29 @@ public class DeviceEventListener extends EventListener { //
                 mRealtimeStatus.put(IRON_STATUS, realtimeData);
 
                 double currentVal = Double.parseDouble(opcData.getData().toString());
+//                double oldVal = ironLoading1.get();
+//
+//                log.info("设置前 - currentVal:{}, ironLoading1_value:{}, thread:{}", currentVal, oldVal, Thread.currentThread().getName());
+//
+                ironLoading1.set(currentVal);
+//
+//                log.info("设置后 - currentVal:{}, ironLoading1_value:{}, thread:{}", currentVal, ironLoading1.get(), Thread.currentThread().getName());
+//
+//                boolean compareResult = Double.compare(oldVal, currentVal) != 0;
+//                boolean greaterThanZero = currentVal > 0;
+//
+//                log.info("条件判断详情 - compareResult:{}, greaterThanZero:{}, 最终结果:{}",
+//                    compareResult, greaterThanZero, (compareResult && greaterThanZero));
 
-                if (ironLoading1.get() <= 0 && currentVal > 0) {
+                if (currentVal > 0) {
+                    log.info("准备调用taphole1Start()");
                     taphole1Start();
-                } else if (ironLoading1.get() > 0 && currentVal <= 0) {
+                } else if (currentVal <= 0) {
+                    log.info("准备调用taphole1End()");
                     taphole1End();
                 }
-
-                ironLoading1.set(Double.parseDouble(opcData.getData().toString()));
-                mContext.setVariable(ExpressionConstants.rtIron01State, ironLoading1.get());
-
+                
+                mContext.setVariable(ExpressionConstants.rtIron01State, currentVal);
             } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_TAPHOLE2_STATUS(opcData.getServerType()))) {
                 ironLoading2.set(Double.parseDouble(opcData.getData().toString()));
                 mContext.setVariable(ExpressionConstants.rtIron02State, ironLoading2.get());
@@ -935,24 +983,33 @@ public class DeviceEventListener extends EventListener { //
             } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_TAPHOLE4_STATUS(opcData.getServerType()))) {
                 ironLoading4.set(Double.parseDouble(opcData.getData().toString()));
                 mContext.setVariable(ExpressionConstants.rtIron04State, ironLoading4.get());
-            } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_IRON_WEIGHT11(opcData.getServerType())) || opcData.getPointName().contains(SubscribeTagConstants.TAG_IRON_WEIGHT12(opcData.getServerType()))) {
-                //铁水流量
-                RealtimeData ironWeight = new RealtimeData();
-                ironWeight.setDesc("铁水流量");
-                ironWeight.setUnit("t");
-                if (ObjectUtils.isEmpty(mIronOPCData) || !opcData.getData().equals(mIronOPCData.getData())) {
-                    mIronOPCData = opcData;
+            } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_IRON_WEIGHT11(opcData.getServerType()))) {
+                // 1号车铁水流量递增值
+                BigDecimal bigDecimalNew = new BigDecimal(opcData.getData().toString());
+                if (bigDecimalNew.compareTo(BigDecimal.ZERO) > 0) {
+                    ironWeight11Current = bigDecimalNew;
+                    if (bigDecimalNew.compareTo(ironWeight11Max) > 0) {
+                        ironWeight11Max = bigDecimalNew;
+                    }
+                } else {
+                    // 罐车卸载,累计最大值到总量
+                    mTotalWeight = mTotalWeight.add(ironWeight11Max);
+                    ironWeight11Max = BigDecimal.ZERO;
+                    ironWeight11Current = BigDecimal.ZERO;
+                }
+            } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_IRON_WEIGHT12(opcData.getServerType()))) {
+                // 2号车铁水流量递增值
+                BigDecimal bigDecimalNew = new BigDecimal(opcData.getData().toString());
+                if (bigDecimalNew.compareTo(BigDecimal.ZERO) > 0) {
+                    ironWeight12Current = bigDecimalNew;
+                    if (bigDecimalNew.compareTo(ironWeight12Max) > 0) {
+                        ironWeight12Max = bigDecimalNew;
+                    }
+                } else {
+                    mTotalWeight = mTotalWeight.add(ironWeight12Max);
+                    ironWeight12Max = BigDecimal.ZERO;
+                    ironWeight12Current = BigDecimal.ZERO;
                 }
-                BigDecimal bigDecimalNew = new BigDecimal(mIronOPCData.getData().toString());
-                mTotalWeight = mTotalWeight.add(bigDecimalNew);
-                ironWeight.setValue(mTotalWeight);
-                mRealtimeData.put(IRON_WEIGHT, ironWeight);
-//                log.info(">>>>>>>>>>>>>{}:{},total:{}", opcData.getPointName(), opcData.getData(), totalWeight.toPlainString());
-                ironWeight.setTime(LocalDateUtils.formatDate(opcData.getServerTime()));
-
-
-                mContext.setVariable(ExpressionConstants.rtIronWeight, mTotalWeight.doubleValue());
-
             } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_FLUSH_STATUS(opcData.getServerType()))) {
                 RealtimeData realtimeData = new RealtimeData();
                 realtimeData.setValue(opcData.getData());
@@ -1000,6 +1057,18 @@ public class DeviceEventListener extends EventListener { //
             mRealtimeData.put(IRON_ELEMENT, realtimeData3);
         }
 
+        // 实时计算总和并推送
+        mTotalWeight = ironWeight11Current.add(ironWeight12Current);
+        RealtimeData ironWeight = new RealtimeData();
+        ironWeight.setDesc("铁水累计流量");
+        ironWeight.setUnit("t");
+        ironWeight.setValue(mTotalWeight);
+        ironWeight.setTime(LocalDateUtils.formatDate(opcData.getServerTime()));
+        mRealtimeData.put(IRON_WEIGHT, ironWeight);
+        mContext.setVariable(ExpressionConstants.rtIronWeight, mTotalWeight.doubleValue());
+
+        //log.info("实时数据:{}", mRealtimeData);
+        //log.info("实时状态:{}", mRealtimeStatus);
         //推送实时数据
         PushData.send2RealtimeData(mRealtimeData);
         //推送实时状态

+ 120 - 0
taphole-opc/src/main/java/com/sckj/opc/controller/THdTagController.java

@@ -1,5 +1,7 @@
 package com.sckj.opc.controller;
 
+import com.baosight.hdjni.HDErrcode;
+import com.baosight.hdsdk.common.HDInterpolationMode;
 import com.sckj.common.aop.Log;
 import com.sckj.common.aop.NotLogin;
 import com.sckj.common.core.AjaxResult;
@@ -227,4 +229,122 @@ public class THdTagController {
         }
     }
 
+    /**
+     * @return
+     * @MethodName: getBasicTagByName
+     * @Description: 查询时间段内的记录总数
+     */
+    @PostMapping("/getRawRecordsCount")
+    @ResponseBody
+    @NotLogin
+    @ApiOperation(value = "查询时间段内的记录总数")
+    public AjaxResult getRawRecordsCount(int tagID, String startTime, String endTime) {
+        try {
+            return AjaxResult.success(ErrorEnum.SUCCESS.getCode(), ErrorEnum.SUCCESS.getMsg(),hdService.getRawRecordsCount(tagID, startTime, endTime));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return AjaxResult.failed(e.getMessage());
+        }
+    }
+
+
+    /**
+     * @return
+     * @MethodName: getBasicTagByName
+     * @Description: 按照记录条数和起始时间查询Tag点原始记录
+     */
+    @PostMapping("/queryTagRawRecordsByCount")
+    @ResponseBody
+    @NotLogin
+    @ApiOperation(value = "按照记录条数和起始时间查询Tag点原始记录")
+    public AjaxResult queryTagRawRecordsByCount(int tagID, String startTime, boolean backward, int recordCount) {
+        try {
+            return AjaxResult.success(hdService.queryTagRawRecordsByCount(tagID, startTime, backward, recordCount));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return AjaxResult.failed(e.getMessage());
+        }
+    }
+
+    /**
+     * @return
+     * @MethodName: getBasicTagByName
+     * @Description: 查询Tag点在某时间段内的原始记录
+     */
+    @PostMapping("/queryTagHisRawRecords")
+    @ResponseBody
+    @NotLogin
+    @ApiOperation(value = "查询Tag点在某时间段内的原始记录")
+    public AjaxResult queryTagHisRawRecords(int tagID, String startTime, String endTime) {
+        try {
+            return AjaxResult.success(hdService.queryTagHisRawRecords(tagID, startTime, endTime));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return AjaxResult.failed(e.getMessage());
+        }
+    }
+    /**
+     * @return
+     * @MethodName: getBasicTagByName
+     * @Description: 按照记录条数和起始时间查询Tag点原始记录
+     */
+    @PostMapping("/queryTagHisInterRecordsByMode")
+    @ResponseBody
+    @NotLogin
+    @ApiOperation(value = "根据不同插值类型,查询Tag点的历史插值记录,查询结果按时间倒序返回")
+    public AjaxResult queryTagHisInterRecordsByMode(int tagID, String startTime, String endTime, long seconds,@RequestParam("mode") HDInterpolationMode mode) {
+        HDErrcode errCode = new HDErrcode();
+        try {
+            return AjaxResult.success(hdService.queryTagHisInterRecordsByMode(tagID, startTime, endTime, seconds * 1000, mode, errCode));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return AjaxResult.failed(errCode);
+        }
+    }
+
+    /**
+     * @return
+     * @MethodName: getBasicTagByName
+     * @Description: 以不同插值算法,查询Tag点的历史插值记录,查询结果按时间正序返回
+     */
+    @PostMapping("/queryTagHisInterRecordsByModeAscending")
+    @ResponseBody
+    @NotLogin
+    @ApiOperation(value = "以不同插值算法,查询Tag点的历史插值记录,查询结果按时间正序返回")
+    public AjaxResult queryTagHisInterRecordsByModeAscending(int tagID, String startTime, String endTime, long seconds,@RequestParam("mode")  HDInterpolationMode mode) {
+        HDErrcode errCode = new HDErrcode();
+        try {
+            return AjaxResult.success(hdService.queryTagHisInterRecordsByModeAscending(tagID, startTime, endTime, seconds * 1000, mode, errCode));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return AjaxResult.failed(errCode);
+        }
+    }
+
+    /**
+     * @return
+     * @MethodName: getBasicTagByName
+     * @Description: 按照记录条数和起始时间查询Tag点原始记录
+     */
+    @PostMapping("/queryTagHisInterRecords")
+    @ResponseBody
+    @NotLogin
+    @ApiOperation(value = "查询Tag点的历史插值记录")
+    public AjaxResult queryTagHisInterRecords(int tagID, String startTime, String endTime, long seconds) {
+        HDErrcode errCode = new HDErrcode();
+        try {
+            return AjaxResult.success(hdService.queryTagHisInterRecords(tagID, startTime, endTime, seconds * 1000, errCode));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return AjaxResult.failed(errCode);
+        }
+    }
+
+
+
+
+
+
+
+
 }

+ 408 - 27
taphole-opc/src/main/java/com/sckj/opc/dataservice/HDServiceImpl.java

@@ -1,10 +1,12 @@
 package com.sckj.opc.dataservice;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baosight.hdjni.HDErrcode;
 import com.baosight.hdsdk.HDConnectionFactory;
 import com.baosight.hdsdk.HDDataProvider;
 import com.baosight.hdsdk.HDServerFactory;
 import com.baosight.hdsdk.HDTagManager;
+import com.baosight.hdsdk.common.HDInterpolationMode;
 import com.baosight.hdsdk.domain.data.HDBasicTag;
 import com.baosight.hdsdk.domain.data.HDDataConnection;
 import com.baosight.hdsdk.domain.data.HDDataServer;
@@ -23,6 +25,7 @@ import com.sckj.opc.service.THdTagServiceImpl;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -30,13 +33,15 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.PreDestroy;
 import javax.annotation.Resource;
 import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 @Service
 @Slf4j
@@ -58,12 +63,32 @@ public class HDServiceImpl {
     @Resource
     private ScheduledTaskManager scheduledTaskManager;
 
-    private ConcurrentHashMap<Long, HDDataConnection> mOPCDaClientMap = new ConcurrentHashMap<>();
-    private ConcurrentHashMap<String, HDRecord> mOPCDaPointsMap = new ConcurrentHashMap<>();
+    private ConcurrentHashMap<Long, HDDataConnection> mIhdClientMap = new ConcurrentHashMap<>();
 
+    // 存储连接状态
+    private ConcurrentHashMap<Long, Boolean> connectionStatusMap = new ConcurrentHashMap<>();
+
+    // 存储上一次的数据值,用于避免推送重复数据
+    private ConcurrentHashMap<String, Object> lastDataMap = new ConcurrentHashMap<>();
+
+    // 心跳检测间隔(秒)
+    private static final int HEARTBEAT_INTERVAL = 30;
+
+    // 最大重试次数
+    private static final int MAX_RETRY_COUNT = 3;
+
+    // 重试间隔(秒)
+    private static final int RETRY_INTERVAL = 5;
 
     //存储ihd标记的定时任务
-    private Set<String> taskNameSet = new HashSet<>();
+    private Set<String> taskNameSet = ConcurrentHashMap.newKeySet();
+
+    // 查询大数据量接口的超时时间(毫秒)
+    private static final long QUERY_TIMEOUT_MS = 10000; // 10秒
+    // 查询大数据量接口的最大返回条数
+    private static final int MAX_RECORD_COUNT = 10000;
+    // 线程池用于超时保护
+    private static final ExecutorService queryExecutor = Executors.newCachedThreadPool();
 
     static {
         boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
@@ -130,6 +155,73 @@ public class HDServiceImpl {
     }
 
     /**
+     * 检查连接状态
+     */
+    private boolean checkConnection(HDDataConnection connection) {
+        try {
+            if (connection != null) {
+                // 尝试执行一个简单的操作来检查连接状态
+                HDDataProvider dp = new HDDataProvider(connection);
+                return true;
+            }
+        } catch (Exception e) {
+            log.error("连接检查失败: {}", e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * 重连机制
+     */
+    private HDDataConnection reconnect(OPCServer opcServer) {
+        log.info("开始重连服务器: {}", opcServer.getIp());
+        int retryCount = 0;
+        while (retryCount < MAX_RETRY_COUNT) {
+            try {
+                HDDataServer server = HDServerFactory.getHDDataServer(opcServer.getIp(), opcServer.getPort(), "", opcServer.getPort());
+                HDDataConnection dataConn = HDConnectionFactory.getHDDataConnection(server);
+                dataConn.loginToServer(opcServer.getUsername(), opcServer.getPassword());
+                log.info("重连成功: {}", opcServer.getIp());
+                return dataConn;
+            } catch (Exception e) {
+                retryCount++;
+                log.error("重连失败,第{}次尝试: {}", retryCount, e.getMessage());
+                if (retryCount < MAX_RETRY_COUNT) {
+                    try {
+                        Thread.sleep(RETRY_INTERVAL * 1000);
+                    } catch (InterruptedException ie) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+        }
+        throw new OperateException("重连失败,已达到最大重试次数");
+    }
+
+    /**
+     * 启动心跳检测
+     */
+    private void startHeartbeat(OPCServer opcServer) {
+        String taskName = "heartbeat_" + opcServer.getId();
+        scheduledTaskManager.addTask(taskName, 0, HEARTBEAT_INTERVAL, TimeUnit.SECONDS, () -> {
+            HDDataConnection connection = mIhdClientMap.get(opcServer.getId());
+            if (!checkConnection(connection)) {
+                log.warn("心跳检测失败,准备重连: {}", opcServer.getIp());
+                try {
+                    HDDataConnection newConnection = reconnect(opcServer);
+                    mIhdClientMap.put(opcServer.getId(), newConnection);
+                    connectionStatusMap.put(opcServer.getId(), true);
+                } catch (Exception e) {
+                    log.error("心跳重连失败: {}", e.getMessage());
+                    connectionStatusMap.put(opcServer.getId(), false);
+                }
+            } else {
+                connectionStatusMap.put(opcServer.getId(), true);
+            }
+        });
+    }
+
+    /**
      * 获得基础的连接信息
      */
     private synchronized HDDataConnection createConnection(OPCServer opcServer) {
@@ -141,7 +233,8 @@ public class HDServiceImpl {
         ) {
             throw new OperateException(String.format("HDSDK服务器信息:【ip:%s,port:%s,username:%s,password:%s】", opcServer.getIp(), opcServer.getPort(), opcServer.getUsername(), opcServer.getPassword()));
         }
-        return mOPCDaClientMap.computeIfAbsent(opcServer.getId(), id -> {
+
+        return mIhdClientMap.computeIfAbsent(opcServer.getId(), id -> {
             log.info("hdsdk【ip:{},port:{},username:{},password:{}】", opcServer.getIp(), opcServer.getPort(), opcServer.getUsername(), opcServer.getPassword());
             HDDataServer server = HDServerFactory.getHDDataServer(opcServer.getIp(), opcServer.getPort(), "", opcServer.getPort());
             HDDataConnection dataConn = null;
@@ -151,9 +244,13 @@ public class HDServiceImpl {
                 log.info("hdsdk init success:{}", LocalDateTime.now());
                 dataConn.loginToServer(opcServer.getUsername(), opcServer.getPassword());
                 log.info("hdsdk connected success:{}", LocalDateTime.now());
+
+                // 启动心跳检测
+                startHeartbeat(opcServer);
+                connectionStatusMap.put(opcServer.getId(), true);
+
                 return dataConn;
             } catch (Exception e) {
-//                e.printStackTrace();
                 if (dataConn != null) {
                     try {
                         dataConn.dispose();
@@ -162,12 +259,12 @@ public class HDServiceImpl {
                         ex.printStackTrace();
                     }
                 }
+                connectionStatusMap.put(opcServer.getId(), false);
                 throw new OperateException("ihdsdk connection failed : " + e.getMessage());
             }
         });
     }
 
-
     private void createSubscription(OPCServer opcServer, THdTag hdTag) {
         if (ObjectUtils.isEmpty(opcServer) || ObjectUtils.isEmpty(hdTag)) {
             throw new OperateException("未获取到服务器或标记信息");
@@ -204,21 +301,28 @@ public class HDServiceImpl {
                         } else {
                             data = Integer.parseInt(formatString);
                         }
-
                     }
 
-                    OPCData build = OPCData.builder()
-                            .data(data)
-                            .pointName(hdTag.getTagName())
-                            .belongTagID(record.getBelongTagID())
-                            .sourceTime(new Date(record.getSecond() * 1000))
-                            .serverTime(new Date(record.getSecond() * 1000))
-                            .serverType(opcServer.getType())
-                            .dataType(hdTag.getTagType())
-                            .identifier(hdTag.getIdentifier())
-                            .build();
-                    asyncEventBus.post(build);
-                    log.info("{}({})当前数据:{}", hdTag.getTagName(), hdTag.getTagDesc(), record.getValueStr());
+                    // 获取上一次的数据值
+                    Object lastData = lastDataMap.get(hdTag.getTagName());
+
+                    // 只有当数据发生变化时才推送
+                    if (!ObjectUtils.equals(data, lastData)) {
+                        OPCData build = OPCData.builder()
+                                .data(data)
+                                .pointName(hdTag.getTagName())
+                                .belongTagID(record.getBelongTagID())
+                                .sourceTime(new Date(record.getSecond() * 1000))
+                                .serverTime(new Date())
+                                .serverType(opcServer.getType())
+                                .dataType(hdTag.getTagType())
+                                .identifier(hdTag.getIdentifier())
+                                .build();
+                        asyncEventBus.post(build);
+                        // 更新缓存中的数据值
+                        lastDataMap.put(hdTag.getTagName(), data);
+                        log.info("{}({})数据更新:{}", hdTag.getTagName(), hdTag.getTagDesc(), record.getValueStr());
+                    }
                 }
             } catch (Exception e) {
                 e.printStackTrace();
@@ -437,7 +541,7 @@ public class HDServiceImpl {
      *   }
      * }
      */
-    public HDBasicTag getBasicTagByName(THdTag hdTag) throws HDSdkException {
+    public HDBasicTag getBasicTagByName(THdTag hdTag) {
         HDBasicTag basicTag = null;
         HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(hdTag);
         if (ObjectUtils.isEmpty(opcPointDTO)) {
@@ -457,16 +561,293 @@ public class HDServiceImpl {
         return basicTag;
     }
 
+    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+
+    /***
+     * 查询时间段内的记录总数
+     * @param tagID tag点id
+     * @param startTime 起始时间
+     * @param endTime 终止时间
+     * @return
+     */
+    public int getRawRecordsCount(int tagID, String startTime, String endTime) {
+        long start = System.currentTimeMillis();
+        try {
+            Future<Integer> future = queryExecutor.submit(() -> {
+                THdTag tHdTag = new THdTag();
+                tHdTag.setTagId(tagID);
+                HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(tHdTag);
+                if (ObjectUtils.isEmpty(opcPointDTO)) {
+                    throw new OperateException(tagID + "未查询到信息");
+                }
+                OPCServer opcServer = new OPCServer();
+                BeanUtils.copyProperties(opcPointDTO, opcServer);
+                opcServer.setId(opcPointDTO.getOpcServerId());
+                HDDataConnection connection = null;
+                try {
+                    connection = createConnection(opcServer);
+                    HDDataProvider dp = new HDDataProvider(connection);
+                    Date st = dateFormat.parse(startTime);
+                    Date et = dateFormat.parse(endTime);
+                    int count = dp.getRawRecordsCount(tagID, st, et);
+                    // 限制最大返回条数
+                    if (count > MAX_RECORD_COUNT) {
+                        count = MAX_RECORD_COUNT;
+                    }
+                    return count;
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            int result = future.get(QUERY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
+            long cost = System.currentTimeMillis() - start;
+            log.info("getRawRecordsCount tagID:{} cost:{}ms", tagID, cost);
+            return result;
+        } catch (TimeoutException e) {
+            log.error("getRawRecordsCount 超时: tagID:{}", tagID);
+            throw new OperateException("查询超时");
+        } catch (Exception e) {
+            log.error("getRawRecordsCount 异常: tagID:{}, error:{}", tagID, e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    /***
+     * 按照记录条数和起始时间查询Tag点原始记录
+     * @param tagID tag点id
+     * @param startTime 起始时间
+     * @param backward 向前还是向后查询;true-向后查询(查询出的记录时间小于起始时间);false-向前查询(记录时间大于起始时间)
+     * @param recordCount 所要查询的总条数,必须在1~65535之间
+     * @return
+     */
+    public List<HDRecord> queryTagRawRecordsByCount(int tagID, String startTime, boolean backward, int recordCount) {
+        long start = System.currentTimeMillis();
+        try {
+            Future<List<HDRecord>> future = queryExecutor.submit(() -> {
+                THdTag tHdTag = new THdTag();
+                tHdTag.setTagId(tagID);
+                HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(tHdTag);
+                if (ObjectUtils.isEmpty(opcPointDTO)) {
+                    throw new OperateException(tagID + "未查询到信息");
+                }
+                OPCServer opcServer = new OPCServer();
+                BeanUtils.copyProperties(opcPointDTO, opcServer);
+                opcServer.setId(opcPointDTO.getOpcServerId());
+                HDDataConnection connection = null;
+                try {
+                    connection = createConnection(opcServer);
+                    HDDataProvider dp = new HDDataProvider(connection);
+                    Date st = dateFormat.parse(startTime);
+                    // 限制最大返回条数
+                    int realCount = Math.min(recordCount, MAX_RECORD_COUNT);
+                    List<HDRecord> RECORDlIST = dp.queryTagRawRecordsByCount(tagID, st, backward, realCount);
+                    return RECORDlIST;
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            List<HDRecord> result = future.get(QUERY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
+            long cost = System.currentTimeMillis() - start;
+            log.info("queryTagRawRecordsByCount tagID:{} count:{} cost:{}ms", tagID, result.size(), cost);
+            return result;
+        } catch (TimeoutException e) {
+            log.error("queryTagRawRecordsByCount 超时: tagID:{}", tagID);
+            throw new OperateException("查询超时");
+        } catch (Exception e) {
+            log.error("queryTagRawRecordsByCount 异常: tagID:{}, error:{}", tagID, e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * 查询Tag点在某时间段内的原始记录
+     *
+     * @param tagID
+     * @param startTime
+     * @param endTime
+     * @return
+     */
+    public List<HDRecord> queryTagHisRawRecords(int tagID, String startTime, String endTime) {
+        long start = System.currentTimeMillis();
+        try {
+            Future<List<HDRecord>> future = queryExecutor.submit(() -> {
+                THdTag tHdTag = new THdTag();
+                tHdTag.setTagId(tagID);
+                HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(tHdTag);
+                if (ObjectUtils.isEmpty(opcPointDTO)) {
+                    throw new OperateException(tagID + "未查询到信息");
+                }
+                OPCServer opcServer = new OPCServer();
+                BeanUtils.copyProperties(opcPointDTO, opcServer);
+                opcServer.setId(opcPointDTO.getOpcServerId());
+                HDDataConnection connection = null;
+                try {
+                    connection = createConnection(opcServer);
+                    HDDataProvider dp = new HDDataProvider(connection);
+                    Date st = dateFormat.parse(startTime);
+                    Date et = dateFormat.parse(endTime);
+                    List<HDRecord> rawRecordsList = new ArrayList<>();
+                    while (true) {
+                        List<HDRecord> tempRawRecordList = dp.queryTagHisRawRecords(tagID, st, et);
+                        rawRecordsList.addAll(tempRawRecordList);
+                        // 限制最大返回条数
+                        if (rawRecordsList.size() >= MAX_RECORD_COUNT) {
+                            return rawRecordsList.subList(0, MAX_RECORD_COUNT);
+                        }
+                        if (tempRawRecordList.size() == 65535) { // 时间范围内记录条数超出65535,以最后一条记录的timestamp作为endTime继续查询
+                            et = dateFormat.parse(rawRecordsList.get(65534).getTimeStampStr());
+                        } else {
+                            break;
+                        }
+                    }
+                    return rawRecordsList;
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            List<HDRecord> result = future.get(QUERY_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
+            long cost = System.currentTimeMillis() - start;
+            log.info("queryTagHisRawRecords tagID:{} count:{} cost:{}ms", tagID, result.size(), cost);
+            return result;
+        } catch (TimeoutException e) {
+            log.error("queryTagHisRawRecords 超时: tagID:{}", tagID);
+            throw new OperateException("查询超时");
+        } catch (Exception e) {
+            log.error("queryTagHisRawRecords 异常: tagID:{}, error:{}", tagID, e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 根据不同插值类型,查询Tag点的历史插值记录,查询结果按时间倒序返回
+     *
+     * @param tagID
+     * @param startTime
+     * @param endTime
+     * @param intervalInMs
+     * @param mode
+     * @param errCode
+     * @return
+     * @throws HDSdkException
+     */
+    public List<ImmutablePair<HDRecord, Integer>> queryTagHisInterRecordsByMode(int tagID, String startTime, String endTime, long intervalInMs, HDInterpolationMode mode, HDErrcode errCode) {
+        THdTag tHdTag = new THdTag();
+        tHdTag.setTagId(tagID);
+        HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(tHdTag);
+        if (ObjectUtils.isEmpty(opcPointDTO)) {
+            throw new OperateException(tagID + "未查询到信息");
+        }
+        OPCServer opcServer = new OPCServer();
+        BeanUtils.copyProperties(opcPointDTO, opcServer);
+        opcServer.setId(opcPointDTO.getOpcServerId());
+        HDDataConnection connection = null;
+        try {
+            connection = createConnection(opcServer);
+            HDDataProvider dp = new HDDataProvider(connection);
+            Date st = dateFormat.parse(startTime);
+            Date et = dateFormat.parse(endTime);
+            List<ImmutablePair<HDRecord, Integer>> interRecordsList = dp.queryTagHisInterRecordsByMode(tagID, st, et, intervalInMs, mode, errCode);
+            return interRecordsList;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 以不同插值算法,查询Tag点的历史插值记录,查询结果按时间正序返回
+     *
+     * @param tagID
+     * @param startTime
+     * @param endTime
+     * @param intervalInMs 插值时间间隔,单位毫秒
+     * @param mode         插值方式,参考HDInterpolationMode
+     *                     <p>
+     *                     可选值:
+     *                     <p>
+     *                     HDInterpolationMode.FORWARD 前向插值方式(当前时刻有值,则取当前时刻的值,当前时刻无值,则取前一个时刻的值)
+     *                     <p>
+     *                     HDInterpolationMode.INVALID 无效插值方式
+     *                     <p>
+     *                     HDInterpolationMode.LINEAR 线性插值方式(当前时刻有值,则取当前时刻的值,当前时刻无值,则根据前一个时刻的值和后一个时刻的值计算线性值)
+     * @param errCode      查询结果的总体错误码,可以根据本参数判断是否为部分成功
+     * @return
+     * @throws HDSdkException
+     */
+    public List<ImmutablePair<HDRecord, Integer>> queryTagHisInterRecordsByModeAscending(int tagID, String startTime, String endTime, long intervalInMs, HDInterpolationMode mode, HDErrcode errCode) {
+        THdTag tHdTag = new THdTag();
+        tHdTag.setTagId(tagID);
+        HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(tHdTag);
+        if (ObjectUtils.isEmpty(opcPointDTO)) {
+            throw new OperateException(tagID + "未查询到信息");
+        }
+        OPCServer opcServer = new OPCServer();
+        BeanUtils.copyProperties(opcPointDTO, opcServer);
+        opcServer.setId(opcPointDTO.getOpcServerId());
+        HDDataConnection connection = null;
+        try {
+            connection = createConnection(opcServer);
+            HDDataProvider dp = new HDDataProvider(connection);
+            Date st = dateFormat.parse(startTime);
+            Date et = dateFormat.parse(endTime);
+            List<ImmutablePair<HDRecord, Integer>> interRecordsList = dp.queryTagHisInterRecordsByModeAscending(tagID, st, et, intervalInMs, mode, errCode);
+            return interRecordsList;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 查询Tag点的历史插值记录
+     *
+     * @param tagID
+     * @param startTime
+     * @param endTime
+     * @param intervalInMs
+     * @param errCode
+     * @return
+     * @throws HDSdkException
+     */
+    public List<ImmutablePair<HDRecord, Integer>> queryTagHisInterRecords(int tagID, String startTime, String endTime, long intervalInMs, HDErrcode errCode) {
+        THdTag tHdTag = new THdTag();
+        tHdTag.setTagId(tagID);
+        HdTagDTO opcPointDTO = hdTagService.selectInfoWithServer(tHdTag);
+        if (ObjectUtils.isEmpty(opcPointDTO)) {
+            throw new OperateException(tagID + "未查询到信息");
+        }
+        OPCServer opcServer = new OPCServer();
+        BeanUtils.copyProperties(opcPointDTO, opcServer);
+        opcServer.setId(opcPointDTO.getOpcServerId());
+        HDDataConnection connection = null;
+        try {
+            connection = createConnection(opcServer);
+            HDDataProvider dp = new HDDataProvider(connection);
+            Date st = dateFormat.parse(startTime);
+            Date et = dateFormat.parse(endTime);
+            List<ImmutablePair<HDRecord, Integer>> interRecordsList = dp.queryTagHisInterRecords(tagID, st, et, intervalInMs, errCode);
+            return interRecordsList;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
     @PreDestroy
     public void releaseServer() {
-        if (ObjectUtils.isNotEmpty(mOPCDaClientMap)) {
-            for (HDDataConnection server : mOPCDaClientMap.values()) {
+        if (ObjectUtils.isNotEmpty(mIhdClientMap)) {
+            for (Map.Entry<Long, HDDataConnection> entry : mIhdClientMap.entrySet()) {
                 try {
-                    server.dispose();
+                    String taskName = "heartbeat_" + entry.getKey();
+                    scheduledTaskManager.cancelTask(taskName);
+                    entry.getValue().dispose();
                 } catch (HDSdkException e) {
                     e.printStackTrace();
                 }
             }
+            mIhdClientMap.clear();
+            connectionStatusMap.clear();
+            lastDataMap.clear();  // 清理数据缓存
         }
     }