yeg
2026-09-08 cee1a4e3bcd2eefea83d242fcfa89ba4fc6d5409
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package com.battery.device.simulator;
 
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import com.battery.device.api.model.DeviceWorkStatus;
 
/** 持久化开发模拟器配置;实现必须在返回前确认写入成功。 */
public interface BtsSimulatorStateStore
{
    int CURRENT_VERSION = 1;
    /** 读取已有状态,仅在不存在时保存默认拓扑。 */
    SimulatorState loadOrInitialize(SimulatorState defaultState);
 
    /** 保存设备配置和观测序号;返回前必须完成写入。 */
    void save(SimulatorState state);
 
    /** 读取短期保留的最近请求。 */
    List<RecentRequest> loadRecentRequests();
 
    /** 保存短期保留的最近请求。 */
    void saveRecentRequests(List<RecentRequest> requests);
 
    /** 人工重置主状态和最近请求。 */
    void reset(SimulatorState defaultState);
 
    record SimulatorState(int version, List<DeviceState> devices, List<CollectorState> collectors,
            int nextDeviceId, int nextCollectorId, List<ChannelTombstone> tombstones,
            Boolean softwareAccessible, long observationSequence)
    {
        public SimulatorState(int version, List<DeviceState> devices, List<CollectorState> collectors,
                int nextDeviceId, int nextCollectorId, List<ChannelTombstone> tombstones)
        {
            this(version, devices, collectors, nextDeviceId, nextCollectorId, tombstones, true, 0L);
        }
 
        public SimulatorState
        {
            if (version != CURRENT_VERSION)
                throw new IllegalStateException("Unsupported BTS simulator state version: " + version);
            softwareAccessible = softwareAccessible == null ? true : softwareAccessible;
            devices = List.copyOf(devices);
            collectors = List.copyOf(collectors);
            tombstones = List.copyOf(tombstones);
        }
    }
 
    record DeviceState(int deviceId, String ip, boolean online, List<ChannelState> channels,
            BtsDeviceProfile profile)
    {
        public DeviceState
        {
            channels = List.copyOf(channels);
            profile = profile == null ? BtsDeviceProfile.virtual(channels.size()) : profile;
        }
 
        /** 兼容旧版只保存拓扑和通道状态的模拟设备。 */
        public DeviceState(int deviceId, String ip, boolean online, List<ChannelState> channels)
        {
            this(deviceId, ip, online, channels, BtsDeviceProfile.virtual(channels.size()));
        }
    }
 
    record CollectorState(int collectorId, String deviceCode, String ip, String mac, String name,
            String manufacturer, String model, int channelCount, boolean online, int sampleIntervalMs,
            List<CollectorChannelState> channels, List<CollectorRealtimeMeasurement> latestMeasurements)
    {
        public CollectorState
        {
            channels = List.copyOf(channels);
            latestMeasurements = List.copyOf(latestMeasurements);
        }
 
        public CollectorState(int collectorId, String deviceCode, String ip, String mac, String name,
                String manufacturer, String model, int channelCount, boolean online)
        {
            this(collectorId, deviceCode, ip, mac, name, manufacturer, model, channelCount, online,
                    DevelopmentCollectorSimulation.DEFAULT_SAMPLE_INTERVAL_MS,
                    DevelopmentCollectorSimulation.defaultChannels(channelCount), List.of());
        }
    }
 
    record CollectorChannelState(String channelCode, List<CollectorMetricState> metrics, DeviceWorkStatus workStatus)
    {
        public CollectorChannelState
        {
            metrics = List.copyOf(metrics);
            workStatus = workStatus == null ? DeviceWorkStatus.WORKING : workStatus;
        }
 
        public CollectorChannelState(String channelCode, List<CollectorMetricState> metrics)
        {
            this(channelCode, metrics, DeviceWorkStatus.WORKING);
        }
    }
 
    record CollectorMetricState(String metricCode, BigDecimal value, String unit) { }
 
    record CollectorRealtimeMeasurement(String adapterCode, String collectorCode, String channelCode,
            long sequenceId, Instant deviceTime, String deviceTimeRaw, Instant serverReceivedAt,
            String metricCode, BigDecimal value, String unit) { }
 
    record ChannelState(int channelId, boolean online, DeviceWorkStatus workStatus,
            ChannelConfiguration configuration)
    {
        public ChannelState
        {
            configuration = configuration == null ? ChannelConfiguration.defaults() : configuration;
        }
 
        public ChannelState(int channelId, boolean online, DeviceWorkStatus workStatus)
        { this(channelId, online, workStatus, null); }
    }
 
    /** 通道当前观测配置与方案一起保存,避免当前工步引用不存在的方案行。 */
    record ChannelConfiguration(BigDecimal currentA, BigDecimal voltageV, BigDecimal capacityAh,
            BigDecimal energyWh, int cycleNo, int stepNo, int plannedCycleCount,
            long totalTimeSeconds, long relativeTimeSeconds, List<SimulatorPlanStep> steps)
    {
        public ChannelConfiguration
        {
            for (BigDecimal value : new BigDecimal[] { currentA, voltageV, capacityAh, energyWh })
                if (value == null || !Double.isFinite(value.doubleValue()))
                    throw new IllegalArgumentException("通道测量值必须是有效数值");
            if (steps == null || steps.isEmpty() || steps.stream().anyMatch(java.util.Objects::isNull))
                throw new IllegalArgumentException("工步方案至少包含一个有效工步");
            steps = List.copyOf(steps);
            if (cycleNo < 1 || plannedCycleCount < cycleNo || stepNo < 1 || stepNo > steps.size()
                    || totalTimeSeconds < 0 || relativeTimeSeconds < 0 || relativeTimeSeconds > totalTimeSeconds)
                throw new IllegalArgumentException("循环、当前工步或时间配置无效");
        }
 
        /** 为新增通道和既有配置补齐固定观测值,不产生成功接收记录。 */
        public static ChannelConfiguration defaults()
        {
            return new ChannelConfiguration(BigDecimal.ZERO, new BigDecimal("3.7"), new BigDecimal("1.2"),
                    new BigDecimal("4.44"), 1, 1, 1, 0, 0,
                    List.of(new SimulatorPlanStep("恒流充电", "cc", 60d, 3.7d)));
        }
    }
 
    record SimulatorPlanStep(String name, String stepType, double durationSeconds, double voltageV)
    {
        public SimulatorPlanStep
        {
            if (name == null || name.isBlank() || stepType == null
                    || !List.of("cc", "dc", "cv", "rest", "end", "cccv").contains(stepType)
                    || !Double.isFinite(durationSeconds) || durationSeconds < 0 || !Double.isFinite(voltageV))
                throw new IllegalArgumentException("工步名称、类型、时间或电压无效");
            name = name.trim();
        }
    }
 
    /** BTS 检测柜的可配置厂商 profile,用于设备发现元数据展示。 */
    record BtsDeviceProfile(int devtype, int subDeviceId, String name, String manufacturer, String model,
            String serialNumber, String firmwareVersion, int channelCount, BigDecimal currentMinA,
            BigDecimal currentMaxA, BigDecimal voltageMinV, BigDecimal voltageMaxV, int sampleIntervalMs)
    {
        public BtsDeviceProfile
        {
            if (devtype < 0 || subDeviceId < 0 || channelCount < 1 || sampleIntervalMs < 1)
                throw new IllegalArgumentException("Invalid BTS device profile limits");
            if (name == null || name.isBlank() || manufacturer == null || manufacturer.isBlank()
                    || model == null || model.isBlank() || serialNumber == null || serialNumber.isBlank()
                    || firmwareVersion == null || firmwareVersion.isBlank())
                throw new IllegalArgumentException("BTS device profile metadata must not be blank");
            if (currentMinA == null || currentMaxA == null || voltageMinV == null || voltageMaxV == null
                    || currentMinA.compareTo(currentMaxA) > 0 || voltageMinV.compareTo(voltageMaxV) > 0)
                throw new IllegalArgumentException("Invalid BTS device profile ranges");
        }
 
        public static BtsDeviceProfile virtual(int channelCount)
        {
            return new BtsDeviceProfile(22, 1, "dev-sim 设备", "开发模拟器", "BTS Virtual Cabinet",
                    "SIM-VIRTUAL", "dev-sim", channelCount, new BigDecimal("-6.000"),
                    new BigDecimal("6.000"), BigDecimal.ZERO, new BigDecimal("5.000"), 100);
        }
    }
 
    record ChannelTombstone(int deviceId, String ip, int channelId, int remainingDiscoveries) { }
 
    record RecentRequest(Instant receivedAt, String command, String request, String response) { }
}