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