package com.battery.device.simulator;
|
|
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 com.battery.device.api.model.DeviceWorkStatus;
|
import com.battery.device.simulator.BtsSimulatorStateStore.*;
|
|
/** 提供请求时的采集器当前值;S 通道仍是待现场验证的开发合同。 */
|
public final class DevelopmentCollectorSimulation
|
{
|
public static final int DEFAULT_SAMPLE_INTERVAL_MS = 1000;
|
private static final ZoneId DEVICE_ZONE = ZoneId.of("Asia/Hong_Kong");
|
private static final DateTimeFormatter DEVICE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
private DevelopmentCollectorSimulation() { }
|
|
/** 创建带稳定 S 通道和三项固定演示指标的采集器状态。 */
|
public static List<CollectorChannelState> defaultChannels(int channelCount)
|
{
|
List<CollectorChannelState> channels = new ArrayList<>(channelCount);
|
for (int index = 1; index <= channelCount; index++)
|
{
|
channels.add(new CollectorChannelState(channelCode(index), List.of(
|
new CollectorMetricState("temperature", new BigDecimal("25.0")
|
.add(BigDecimal.valueOf(index, 1)), "℃"),
|
new CollectorMetricState("positive_potential", new BigDecimal("3.700"), "V"),
|
new CollectorMetricState("negative_potential", new BigDecimal("-0.100"), "V"))));
|
}
|
return List.copyOf(channels);
|
}
|
|
|
/** 调整设备拓扑,保留已有通道的配置。 */
|
public static CollectorState resize(CollectorState current, String ip, int channelCount, boolean online, Instant changedAt)
|
{
|
var defaults = defaultChannels(channelCount);
|
List<CollectorChannelState> channels = new ArrayList<>();
|
for (int index = 0; index < channelCount; index++)
|
channels.add(index < current.channels().size() ? current.channels().get(index) : defaults.get(index));
|
return new CollectorState(current.collectorId(), current.deviceCode(), ip.trim(), current.mac(),
|
current.name(), current.manufacturer(), current.model(), channelCount, online,
|
current.sampleIntervalMs(), channels, List.of());
|
}
|
|
/** 保存当前测量配置,等待下一次实时请求。 */
|
public static CollectorState configureChannel(CollectorState current, String channelCode, List<CollectorMetricState> metrics)
|
{
|
validateMetrics(metrics);
|
int index = parseChannelIndex(channelCode, current.channelCount());
|
List<CollectorChannelState> channels = new ArrayList<>(current.channels());
|
channels.set(index, new CollectorChannelState(channelCode, metrics, channels.get(index).workStatus()));
|
return copy(current, channels, List.of());
|
}
|
|
/** 保存通道工作状态,等待下一次实时请求。 */
|
public static CollectorState configureStatus(CollectorState current, String channelCode, DeviceWorkStatus status)
|
{
|
int index = parseChannelIndex(channelCode, current.channelCount());
|
List<CollectorChannelState> channels = new ArrayList<>(current.channels());
|
channels.set(index, new CollectorChannelState(channelCode, channels.get(index).metrics(), status));
|
return copy(current, channels, List.of());
|
}
|
|
/** 仅生成本次请求的当前值,不补算上一次请求与本次之间的空档。 */
|
public static CollectorState sample(CollectorState current, Instant now, long sequence)
|
{
|
Instant observedAt = now.truncatedTo(java.time.temporal.ChronoUnit.MILLIS);
|
List<CollectorRealtimeMeasurement> measurements = new ArrayList<>();
|
for (CollectorChannelState channel : current.channels())
|
{
|
if (channel.workStatus() == DeviceWorkStatus.UNOBSERVABLE) continue;
|
for (CollectorMetricState metric : channel.metrics())
|
measurements.add(new CollectorRealtimeMeasurement("dev-sim", current.deviceCode(), channel.channelCode(),
|
sequence, observedAt, DEVICE_TIME_FORMATTER.format(observedAt.atZone(DEVICE_ZONE)), observedAt,
|
metric.metricCode(), metric.value(), metric.unit()));
|
}
|
return copy(current, current.channels(), measurements);
|
}
|
|
/** 校验恢复后的稳定通道和测量配置。 */
|
public static void validate(CollectorState state)
|
{
|
if (state.channels().size() != state.channelCount()) throw new IllegalStateException("Invalid collector topology");
|
for (int index = 0; index < state.channels().size(); index++)
|
{
|
var channel = state.channels().get(index);
|
if (!channelCode(index + 1).equals(channel.channelCode())) throw new IllegalStateException("Invalid collector channel identity");
|
validateMetrics(channel.metrics());
|
}
|
}
|
|
private static CollectorState copy(CollectorState current, List<CollectorChannelState> channels,
|
List<CollectorRealtimeMeasurement> measurements)
|
{
|
return new CollectorState(current.collectorId(), current.deviceCode(), current.ip(), current.mac(),
|
current.name(), current.manufacturer(), current.model(), current.channelCount(), current.online(),
|
current.sampleIntervalMs(), channels, measurements);
|
}
|
|
private static void validateMetrics(List<CollectorMetricState> metrics)
|
{
|
if (metrics == null || metrics.isEmpty())
|
throw new IllegalArgumentException("Collector channel must contain at least one metric");
|
java.util.HashSet<String> codes = new java.util.HashSet<>();
|
for (CollectorMetricState metric : metrics)
|
{
|
if (metric.metricCode() == null || metric.metricCode().isBlank() || metric.value() == null
|
|| !metric.metricCode().equals(metric.metricCode().trim())
|
|| metric.unit() == null || metric.unit().isBlank()
|
|| !metric.unit().equals(metric.unit().trim()) || !codes.add(metric.metricCode()))
|
throw new IllegalArgumentException("Collector metric code, value and unit must be valid and unique");
|
}
|
}
|
|
private static int parseChannelIndex(String value, int channelCount)
|
{
|
if (value == null || !value.matches("S-\\d{2}"))
|
throw new IllegalArgumentException("Collector channel must use S-01...S-N identity");
|
int index = Integer.parseInt(value.substring(2)) - 1;
|
if (index < 0 || index >= channelCount || !channelCode(index + 1).equals(value))
|
throw new IllegalArgumentException("Unknown collector channel: " + value);
|
return index;
|
}
|
|
private static String channelCode(int index)
|
{
|
return String.format("S-%02d", index);
|
}
|
|
public record CollectorRealtimeSnapshot(String collectorCode, boolean online, int sampleIntervalMs,
|
List<CollectorRealtimeMeasurement> measurements)
|
{
|
public CollectorRealtimeSnapshot { measurements = List.copyOf(measurements); }
|
}
|
|
}
|