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) { }
|
}
|