From 9c0fd80309b277420c492180ae9f98f779f0f5c8 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 30 八月 2026 23:29:33 +0800
Subject: [PATCH] 实现设备自然结束与区间补采恢复

---
 ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java |  102 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 102 insertions(+), 0 deletions(-)

diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
index e66c092..7918e0c 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
@@ -4,6 +4,8 @@
 import java.io.StringReader;
 import java.math.BigDecimal;
 import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 import javax.xml.stream.XMLInputFactory;
@@ -22,6 +24,7 @@
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorState;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.DeviceState;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.BtsDeviceProfile;
+import com.ruoyi.device.simulator.BtsSimulatorStateStore.BtsHistoryRecord;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.RecentRequest;
 import com.ruoyi.device.simulator.BtsSimulatorStateStore.SimulatorState;
 import com.ruoyi.device.simulator.InMemoryBtsSimulatorStateStore;
@@ -50,6 +53,9 @@
     private SimulatorState state;
     private boolean running = true;
     private long observationSequence;
+    private final java.util.Map<String, java.util.List<SimBtsHistory>> btsHistory = new java.util.LinkedHashMap<>();
+    private static final DateTimeFormatter BTS_HISTORY_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH.mm.ss")
+            .withZone(ZoneId.of("Asia/Hong_Kong"));
 
     /** 启动仅监听 IPv4 本机回环地址的开发模拟器;传入 0 时由系统分配测试端口。 */
     public BtsDevelopmentSimulator(int port) throws IOException
@@ -62,7 +68,9 @@
     {
         this.stateStore = stateStore;
         state = stateStore.loadOrInitialize(defaultState());
+        // 仅清理 Ticket13 之前嵌在主状态中的旧演示历史;独立历史由存储实现保留并继续补采。
         state = stateStore.resetHistoryForDemoStartup(state);
+        restoreBtsHistory();
         validateRestoredState();
         stateStore.completeValidatedRestore(state);
         simulator = ScriptedBtsTcpSimulator.responding(port, this::respond);
@@ -341,6 +349,8 @@
         {
             case "getdevinfo" -> deviceInfoResponse();
             case "inquire" -> observationResponse(request);
+            case "inquiredf" -> historyAvailabilityResponse(request);
+            case "download" -> historyResponse(request);
             default -> "<bts version=\"1.0\"><cmd>error</cmd></bts>";
         };
     }
@@ -445,6 +455,16 @@
                     profile.voltageMaxV().doubleValue());
             double capacity = working ? Math.max(0d, tick * 0.001d * current) : 0d;
             double energy = capacity * voltage;
+            if (working)
+            {
+                String key = historyKey(profile.devtype(), observed.deviceId(), profile.subDeviceId(), channel.channelId());
+                java.util.List<SimBtsHistory> history = btsHistory.computeIfAbsent(key, ignored -> new ArrayList<>());
+                history.add(new SimBtsHistory(history.size() + 1L, Instant.now(), current, voltage, capacity, energy));
+                SimBtsHistory latest = history.get(history.size() - 1);
+                stateStore.appendBtsHistory(key, new BtsHistoryRecord(latest.sequence(), latest.at(), latest.current(),
+                        latest.voltage(), latest.capacity(), latest.energy()));
+                if (history.size() > BtsSimulatorStateStore.MAX_HISTORY_RECORDS) history.remove(0);
+            }
             response.append("<inquire dev=\"").append(profile.devtype()).append('-').append(observed.deviceId())
                     .append('-').append(profile.subDeviceId()).append('-').append(channel.channelId())
                     .append("-0\" cycle_id=\"1\" step_id=\"1\" step_type=\"cc\" workstatus=\"")
@@ -458,6 +478,88 @@
         return response.append("</list></bts>").toString();
     }
 
+    private synchronized String historyAvailabilityResponse(String request)
+    {
+        java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("<chl\\s+([^>]*)/?>").matcher(request);
+        StringBuilder response = new StringBuilder("<bts version=\"1.0\"><cmd>inquiredf_resp</cmd><list>");
+        while (matcher.find())
+        {
+            String attrs = matcher.group(1);
+            int devtype = Integer.parseInt(attribute(attrs, "devtype"));
+            int devid = Integer.parseInt(attribute(attrs, "devid"));
+            int subdevid = Integer.parseInt(attribute(attrs, "subdevid"));
+            int chlid = Integer.parseInt(attribute(attrs, "chlid"));
+            int count = btsHistory.getOrDefault(historyKey(devtype, devid, subdevid, chlid), List.of()).size();
+            response.append("<chl devtype=\"").append(devtype).append("\" devid=\"").append(devid)
+                    .append("\" subdevid=\"").append(subdevid).append("\" chlid=\"").append(chlid)
+                    .append("\" testid=\"SIM-").append(devid).append('-').append(chlid).append("\" count=\"")
+                    .append(count).append("\">true</chl>");
+        }
+        return response.append("</list></bts>").toString();
+    }
+
+    private synchronized String historyResponse(String request)
+    {
+        int devtype = Integer.parseInt(attribute(request, "devtype"));
+        int devid = Integer.parseInt(attribute(request, "devid"));
+        int subdevid = Integer.parseInt(attribute(request, "subdevid"));
+        int chlid = Integer.parseInt(attribute(request, "chlid"));
+        String testId = attribute(request, "testid");
+        long start = Long.parseLong(attribute(request, "startpos"));
+        int count = Integer.parseInt(attribute(request, "count"));
+        List<SimBtsHistory> history = btsHistory.getOrDefault(historyKey(devtype, devid, subdevid, chlid), List.of());
+        int from = (int) Math.min(Math.max(0L, start - 1L), history.size());
+        int to = Math.min(history.size(), from + Math.max(1, count));
+        StringBuilder response = new StringBuilder("<bts version=\"1.0\"><cmd>download_resp</cmd><download devtype=\"")
+                .append(devtype).append("\" devid=\"").append(devid).append("\" subdevid=\"").append(subdevid)
+                .append("\" chlid=\"").append(chlid).append("\" testid=\"").append(testId)
+                .append("\"><list>");
+        for (SimBtsHistory item : history.subList(from, to))
+            response.append("<data seqid=\"").append(item.sequence()).append("\" stepid=\"1\" cycleid=\"1\" steptype=\"cc\" atime=\"")
+                    .append(BTS_HISTORY_TIME.format(item.at())).append("\" testtime=\"1000\" volt=\"")
+                    .append(decimal(item.voltage())).append("\" curr=\"").append(decimal(item.current()))
+                    .append("\" cap=\"").append(decimal(item.capacity())).append("\" eng=\"").append(decimal(item.energy())).append("\"/>");
+        return response.append("</list></download></bts>").toString();
+    }
+
+    private String attribute(String xml, String name)
+    {
+        java.util.regex.Matcher matcher = java.util.regex.Pattern.compile(name + "=\\\"([^\\\"]+)\\\"").matcher(xml);
+        if (!matcher.find()) throw new IllegalArgumentException("Missing simulator attribute: " + name);
+        return matcher.group(1);
+    }
+
+    private String historyKey(int devtype, int devid, int subdevid, int chlid)
+    {
+        return devtype + ":" + devid + ":" + subdevid + ":" + chlid;
+    }
+
+    private void restoreBtsHistory()
+    {
+        for (DeviceState device : state.devices())
+        {
+            BtsDeviceProfile profile = profileFor(device);
+            for (ChannelState channel : device.channels())
+            {
+                String key = historyKey(profile.devtype(), device.deviceId(), profile.subDeviceId(), channel.channelId());
+                ArrayList<SimBtsHistory> restored = new ArrayList<>();
+                long offset = 0;
+                while (true)
+                {
+                    var page = stateStore.loadBtsHistoryPage(key, offset, BtsSimulatorStateStore.MAX_HISTORY_BATCH_SIZE);
+                    restored.addAll(page.records().stream().map(item -> new SimBtsHistory(item.sequenceId(),
+                            item.deviceTime(), item.currentA(), item.voltageV(), item.capacityAh(), item.energyWh())).toList());
+                    if (!page.hasMore()) break;
+                    if (page.nextOffset() <= offset) throw new IllegalStateException("Invalid BTS history page");
+                    offset = page.nextOffset();
+                }
+                if (!restored.isEmpty()) btsHistory.put(key, restored);
+            }
+        }
+    }
+
+    private record SimBtsHistory(long sequence, Instant at, double current, double voltage, double capacity, double energy) { }
+
     private java.util.Set<String> requestedChannels(String request)
     {
         java.util.Set<String> channels = new java.util.HashSet<>();

--
Gitblit v1.7.1