yeg
2026-08-31 6c38637d89f346a660618558451f2dd7286dc7a5
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
package com.ruoyi.device.simulator;
 
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import com.ruoyi.device.model.DeviceWorkStatus;
 
/** 持久化开发模拟器配置;实现必须在返回前确认写入成功。 */
public interface BtsSimulatorStateStore
{
    int CURRENT_VERSION = 1;
    int MAX_HISTORY_BATCH_SIZE = 1000;
    /** 开发演示只保留有限历史,避免 Redis 随运行时间无限增长。 */
    long MAX_HISTORY_RECORDS = 20_000L;
 
    /** 读取已有状态;仅在状态不存在时原子写入并返回默认状态。 */
    SimulatorState loadOrInitialize(SimulatorState defaultState);
 
    /** 开发模拟器启动时清理上一轮演示历史;正式业务存储不需要实现该动作。 */
    default SimulatorState resetHistoryForDemoStartup(SimulatorState state) { return state; }
 
    /** 完整保存下一版模拟器状态。 */
    void save(SimulatorState state);
 
    /** 状态和独立历史全部校验通过后,提交兼容旧格式所需的迁移。 */
    default void completeValidatedRestore(SimulatorState state) { }
 
    /** 原子保存主状态并追加本轮新产生的原始历史。 */
    default void save(SimulatorState state, List<CollectorHistoryAppend> historyAppends)
    {
        if (!historyAppends.isEmpty())
            throw new UnsupportedOperationException("Collector history append is not supported");
        save(state);
    }
 
    /** 按原始写入顺序或反向顺序读取有界历史批次。 */
    default CollectorHistoryBatch loadCollectorHistoryPage(String collectorCode, long offset, int limit,
            boolean reverse)
    {
        if (offset < 0) throw new IllegalArgumentException("offset must not be negative");
        if (limit < 1 || limit > MAX_HISTORY_BATCH_SIZE)
            throw new IllegalArgumentException("limit must be between 1 and " + MAX_HISTORY_BATCH_SIZE);
        return new CollectorHistoryBatch(offset, 0, false, 0, List.of());
    }
 
    /** 读取检测柜模拟历史;正式适配器不依赖该演示扩展。 */
    default BtsHistoryBatch loadBtsHistoryPage(String historyKey, long offset, int limit)
    {
        return new BtsHistoryBatch(offset, 0, false, 0, List.of());
    }
 
    /** 追加检测柜模拟历史,供重启续采合同使用。 */
    default void appendBtsHistory(String historyKey, BtsHistoryRecord record) { }
 
    /** 读取检测柜通道的播放状态;实现可在不支持播放持久化时返回 null。 */
    default BtsPlaybackState loadBtsPlaybackState(String historyKey) { return null; }
 
    /** 保存检测柜通道的播放状态。 */
    default void saveBtsPlaybackState(String historyKey, BtsPlaybackState state) { }
 
    /** 删除检测柜通道的播放状态。 */
    default void deleteBtsPlaybackState(String historyKey) { }
 
    /** 删除检测柜通道的持久化历史和对应索引。 */
    default void deleteBtsHistory(String historyKey) { }
 
    /** 原子提交检测柜新增历史与推进后的播放状态。 */
    default void saveBtsPlaybackAndAppendHistory(String historyKey, BtsPlaybackState state,
            List<BtsHistoryRecord> records)
    {
        for (BtsHistoryRecord record : records) appendBtsHistory(historyKey, record);
        saveBtsPlaybackState(historyKey, state);
    }
 
    /** 原子保存删除采集器后的主状态,并删除该采集器的独立历史。 */
    default void saveAndDeleteCollectorHistory(SimulatorState state, String collectorCode)
    {
        throw new UnsupportedOperationException("Atomic collector deletion is not supported");
    }
 
    /** 读取短期保留的最近请求。 */
    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)
    {
        public SimulatorState
        {
            if (version != CURRENT_VERSION)
                throw new IllegalStateException("Unsupported BTS simulator state version: " + version);
            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,
            long nextSequence, Instant lastSampledAt, List<CollectorChannelState> channels,
            List<CollectorRealtimeMeasurement> latestMeasurements,
            long historyRecordCount, CollectorHistoryFaults historyFaults)
    {
        public CollectorState
        {
            channels = List.copyOf(channels);
            latestMeasurements = List.copyOf(latestMeasurements);
            historyFaults = historyFaults == null ? CollectorHistoryFaults.none() : historyFaults;
        }
 
        /** 兼容只包含发现配置的旧模拟状态;Redis 读取边界会补齐采样合同。 */
        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, 1L, null,
                    DevelopmentCollectorSimulation.defaultChannels(channelCount), List.of(), 0L,
                    CollectorHistoryFaults.none());
        }
    }
 
    record CollectorChannelState(String channelCode, List<CollectorMetricState> metrics)
    {
        public CollectorChannelState { metrics = List.copyOf(metrics); }
    }
 
    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 CollectorMeasurementRecord(String adapterCode, String collectorCode, String channelCode,
            long sequenceId, Instant deviceTime, String deviceTimeRaw, Instant serverReceivedAt,
            String metricCode, BigDecimal value, String unit) { }
 
    record CollectorHistoryAppend(String collectorCode, List<CollectorMeasurementRecord> records)
    {
        public CollectorHistoryAppend { records = List.copyOf(records); }
    }
 
    record CollectorHistoryBatch(long offset, long nextOffset, boolean hasMore, long total,
            List<CollectorMeasurementRecord> records)
    {
        public CollectorHistoryBatch { records = List.copyOf(records); }
    }
 
    record BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV,
            double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType)
    {
        /** 兼容 Ticket13 之前仅包含核心测量值的历史记录。 */
        public BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV,
                double capacityAh, double energyWh)
        {
            this(sequenceId, deviceTime, currentA, voltageV, capacityAh, energyWh, 1, 1, "恒流充电", "cc");
        }
    }
 
    /** 检测柜单通道演示播放状态;与测试的接收状态相互独立。 */
    record BtsPlaybackState(boolean playing, int speedMultiplier, int cycleNo, int stepNo,
            String stepName, String stepType, Instant simulatedAt, Instant wallClockAt,
            long nextSequence, boolean naturallyFinished)
    {
        public BtsPlaybackState
        {
            if (speedMultiplier != 1 && speedMultiplier != 60 && speedMultiplier != 600)
                throw new IllegalArgumentException("speedMultiplier must be 1, 60 or 600");
            if (cycleNo < 1 || stepNo < 1 || nextSequence < 1 || stepName == null || stepName.isBlank()
                    || stepType == null || stepType.isBlank())
                throw new IllegalArgumentException("Invalid BTS playback state");
        }
    }
 
    record BtsHistoryBatch(long offset, long nextOffset, boolean hasMore, long total,
            List<BtsHistoryRecord> records)
    {
        public BtsHistoryBatch { records = List.copyOf(records); }
    }
 
    record CollectorHistoryFaults(List<Long> missingSequences, Long duplicateSequence,
            Long conflictingSequence, boolean outOfOrder)
    {
        public CollectorHistoryFaults
        {
            missingSequences = missingSequences == null ? List.of() : List.copyOf(missingSequences);
        }
 
        public static CollectorHistoryFaults none()
        {
            return new CollectorHistoryFaults(List.of(), null, null, false);
        }
    }
 
    record ChannelState(int channelId, boolean online, DeviceWorkStatus workStatus) { }
 
    /** 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) { }
}