From f522c9ffa487e3710d691e8b98df8c4f0cfafd43 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 05 九月 2026 20:07:26 +0800
Subject: [PATCH] 支持模拟器逐通道参数与工步方案配置

---
 battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java |   72 +++++++++++++++++++++++++-----------
 1 files changed, 50 insertions(+), 22 deletions(-)

diff --git a/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java b/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
index f20ee73..d0ba2fb 100644
--- a/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
+++ b/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
@@ -23,6 +23,7 @@
 import com.battery.device.api.TestDataObservationAdapter;
 import com.battery.device.simulator.BtsSimulatorStateStore;
 import com.battery.device.simulator.BtsSimulatorStateStore.ChannelState;
+import com.battery.device.simulator.BtsSimulatorStateStore.ChannelConfiguration;
 import com.battery.device.simulator.BtsSimulatorStateStore.ChannelTombstone;
 import com.battery.device.simulator.BtsSimulatorStateStore.CollectorChannelState;
 import com.battery.device.simulator.BtsSimulatorStateStore.CollectorMetricState;
@@ -218,10 +219,26 @@
         if (channelId < 1 || channelId > device.channels().size())
             throw new IllegalArgumentException("channelId must be between 1 and " + device.channels().size());
         List<ChannelState> channels = new ArrayList<>(device.channels());
-        channels.set(channelId - 1, new ChannelState(channelId, online, workStatus));
+        channels.set(channelId - 1, new ChannelState(channelId, online, workStatus,
+                channels.get(channelId - 1).configuration()));
         persist(new SimulatorState(state.version(),
                 replaceDevice(new DeviceState(deviceId, device.ip(), device.online(), channels, device.profile())), state.collectors(),
                 state.nextDeviceId(), state.nextCollectorId(), state.tombstones()));
+    }
+
+    /** 保存单通道的当前测量和工步方案,不改写在线或工作状态,不产生观测。 */
+    public synchronized void configureChannel(int deviceId, int channelId, ChannelConfiguration configuration)
+    {
+        if (configuration == null) throw new IllegalArgumentException("通道配置不能为空");
+        DeviceState device = requireDevice(deviceId);
+        if (channelId < 1 || channelId > device.channels().size())
+            throw new IllegalArgumentException("通道不存在");
+        List<ChannelState> channels = new ArrayList<>(device.channels());
+        ChannelState channel = channels.get(channelId - 1);
+        channels.set(channelId - 1, new ChannelState(channelId, channel.online(), channel.workStatus(), configuration));
+        persist(new SimulatorState(state.version(),
+                replaceDevice(new DeviceState(deviceId, device.ip(), device.online(), channels, device.profile())),
+                state.collectors(), state.nextDeviceId(), state.nextCollectorId(), state.tombstones()));
     }
 
     /** 新增一台与 BTS 检测柜拓扑隔离的开发采集器。 */
@@ -297,14 +314,29 @@
         replaceCollector(DevelopmentCollectorSimulation.configureStatus(requireCollector(collectorId), channelCode, status));
     }
 
-    /** 返回固定的开发计划,不推进工步或生成历史。 */
+    /** 返回通道已配置的开发方案及当前工步位置,不自动推进或生成历史。 */
     @Override
     public synchronized TestPlan testPlan(ChannelIdentity channel)
     {
         if (!canObserve(channel)) return null;
-        return new TestPlan(1, 1, 1, List.of(new PlanRow("STEP", 1, 1, "恒流充电", 60d, 3.7d,
-                requireDevice(channel.devid()).channels().get(channel.chlid() - 1).workStatus() == DeviceWorkStatus.FINISHED
-                        ? "已完成" : "待执行", null, null)));
+        ChannelState source = requireDevice(channel.devid()).channels().get(channel.chlid() - 1);
+        ChannelConfiguration configuration = source.configuration();
+        List<PlanRow> rows = new ArrayList<>();
+        for (int index = 0; index < configuration.steps().size(); index++) {
+            var step = configuration.steps().get(index);
+            String status = index + 1 < configuration.stepNo() ? "已完成" : "待执行";
+            if (index + 1 == configuration.stepNo()) status = switch (source.workStatus()) {
+                case WORKING -> "进行中";
+                case PAUSED -> "暂停";
+                case FINISHED -> "已完成";
+                case PROTECTED -> "异常";
+                default -> "待执行";
+            };
+            // cyclePosition 表示循环内的工步位置,不能使用当前循环号,否则控制台会合并不同工步。
+            rows.add(new PlanRow("STEP", index + 1, index + 1, step.name(), step.durationSeconds(),
+                    step.voltageV(), status, null, null));
+        }
+        return new TestPlan(configuration.cycleNo(), configuration.stepNo(), configuration.plannedCycleCount(), rows);
     }
 
     /** 请求当前检测柜测量;离线或无法观测时不返回缓存值。 */
@@ -324,14 +356,13 @@
         if (channel.workStatus() == DeviceWorkStatus.UNOBSERVABLE) return null;
         Instant now = Instant.now().truncatedTo(java.time.temporal.ChronoUnit.MILLIS);
         long sequence = nextObservationSequence();
-        double wave = Math.sin((sequence + channel.channelId()) * 0.7d);
-        BtsDeviceProfile profile = profileFor(device);
-        double current = channel.workStatus() == DeviceWorkStatus.WORKING
-                ? clamp(profile.currentMaxA().doubleValue() * (0.18d + wave * 0.02d),
-                        profile.currentMinA().doubleValue(), profile.currentMaxA().doubleValue()) : 0d;
-        double voltage = clamp(3.7d + wave * 0.01d, profile.voltageMinV().doubleValue(), profile.voltageMaxV().doubleValue());
-        CabinetRealtime result = new CabinetRealtime(sequence, now, current, voltage, 1.2d, 4.44d,
-                1, 1, "恒流充电", "cc", now, DEVICE_TIME.format(now.atZone(DEVICE_ZONE)), DEVICE_ZONE.getId());
+        ChannelConfiguration configuration = channel.configuration();
+        var step = configuration.steps().get(configuration.stepNo() - 1);
+        // 保存值直接成为当前观测;工作状态和设备量程不反向覆盖操作员配置。
+        CabinetRealtime result = new CabinetRealtime(sequence, now, configuration.currentA().doubleValue(),
+                configuration.voltageV().doubleValue(), configuration.capacityAh().doubleValue(), configuration.energyWh().doubleValue(),
+                configuration.cycleNo(), configuration.stepNo(), step.name(), step.stepType(),
+                now, DEVICE_TIME.format(now.atZone(DEVICE_ZONE)), DEVICE_ZONE.getId());
         return result;
     }
 
@@ -406,7 +437,7 @@
                 .map(device -> new DeviceSnapshot(device.deviceId(), device.ip(), device.online(), device.profile(),
                         device.channels().stream()
                                 .map(channel -> new ChannelSnapshot(channel.channelId(), channel.online(),
-                                        channel.workStatus()))
+                                        channel.workStatus(), channel.configuration()))
                                 .toList()))
                 .toList();
         List<CollectorSnapshot> collectorSnapshots = state.collectors().stream()
@@ -581,7 +612,8 @@
                     .append("\" current=\"").append(decimal(current)).append("\" voltage=\"")
                     .append(decimal(voltage)).append("\" capacity=\"").append(decimal(capacity))
                     .append("\" energy=\"").append(decimal(energy)).append("\" totaltime=\"")
-                    .append(0).append("\" relativetime=\"").append(0)
+                    .append(channel.configuration().totalTimeSeconds()).append("\" relativetime=\"")
+                    .append(channel.configuration().relativeTimeSeconds())
                     .append("\" open_or_close=\"0\" log_code=\"0\">")
                     // 模拟器返回当前设备方案,供统一观测模型展示;不代表真实 BTS 的完整方案字段。
                     .append("<plan><step step_no=\"").append(stepNo).append("\" step_type=\"")
@@ -662,14 +694,9 @@
         throw new IllegalArgumentException("Unsupported Neware simulator model: " + model);
     }
 
-    private double clamp(double value, double minimum, double maximum)
-    {
-        return Math.max(minimum, Math.min(maximum, value));
-    }
-
     private String decimal(double value)
     {
-        return String.format(java.util.Locale.ROOT, "%.3f", value);
+        return java.math.BigDecimal.valueOf(value).toPlainString();
     }
 
     private String toBtsWorkStatus(DeviceWorkStatus status)
@@ -848,7 +875,8 @@
     public record DeviceSnapshot(int deviceId, String ip, boolean online, BtsDeviceProfile profile,
             List<ChannelSnapshot> channels) { }
 
-    public record ChannelSnapshot(int channelId, boolean online, DeviceWorkStatus workStatus) { }
+    public record ChannelSnapshot(int channelId, boolean online, DeviceWorkStatus workStatus,
+            ChannelConfiguration configuration) { }
 
     public record CollectorSnapshot(int collectorId, String deviceCode, String ip, String mac, String name,
             String manufacturer, String model, int channelCount, boolean online, int sampleIntervalMs,

--
Gitblit v1.7.1