package com.battery.business.web.device;
|
|
import java.math.BigDecimal;
|
import java.util.List;
|
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.Resource;
|
import org.springframework.http.HttpStatus;
|
import org.springframework.http.MediaType;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PutMapping;
|
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.context.annotation.Profile;
|
import com.ruoyi.common.annotation.Anonymous;
|
import com.battery.device.simulator.BtsDevelopmentSimulator;
|
import com.battery.device.simulator.BtsDevelopmentSimulator.StateSnapshot;
|
import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore;
|
import com.battery.device.api.model.DeviceWorkStatus;
|
import com.battery.device.simulator.BtsSimulatorStateStore.CollectorHistoryFaults;
|
import com.battery.device.simulator.BtsSimulatorStateStore.CollectorMetricState;
|
import com.battery.device.simulator.DevelopmentCollectorSimulation.CollectorHistoryPage;
|
import com.battery.device.simulator.DevelopmentCollectorSimulation.CollectorRealtimeSnapshot;
|
import com.battery.device.simulator.BtsDevelopmentSimulator.PlaybackSnapshot;
|
|
/** 本机临时 BTS 模拟器控制 API。 */
|
@Anonymous
|
@RestController
|
@Profile("dev-simulator")
|
public class BtsDevelopmentSimulatorController
|
{
|
private final BtsDevelopmentSimulator simulator;
|
private final DeviceDiscoveryCandidateStore candidateStore;
|
private final com.battery.business.runtime.DevelopmentCollectorStatusSynchronizer statusSynchronizer;
|
|
@Autowired
|
public BtsDevelopmentSimulatorController(BtsDevelopmentSimulator simulator,
|
DeviceDiscoveryCandidateStore candidateStore,
|
com.battery.business.runtime.DevelopmentCollectorStatusSynchronizer statusSynchronizer)
|
{
|
this.simulator = simulator;
|
this.candidateStore = candidateStore;
|
this.statusSynchronizer = statusSynchronizer;
|
}
|
|
/** 保留独立模拟器测试和工具调用的构造入口,不触发数据库同步。 */
|
public BtsDevelopmentSimulatorController(BtsDevelopmentSimulator simulator,
|
DeviceDiscoveryCandidateStore candidateStore)
|
{
|
this(simulator, candidateStore, null);
|
}
|
|
/** 返回临时控制页;匿名放行后仍由本机拦截器限制来源。 */
|
@GetMapping(value = "/dev/bts-simulator.html", produces = MediaType.TEXT_HTML_VALUE)
|
public Resource page()
|
{
|
return new ClassPathResource("static/dev/bts-simulator.html");
|
}
|
|
/** 返回模拟器、虚拟设备、通道和最近请求的当前快照。 */
|
@GetMapping("/dev/bts-simulator/state")
|
public StateSnapshot state()
|
{
|
return simulator.snapshot();
|
}
|
|
/** 人工清空长期状态并恢复一台默认检测柜和采集器。 */
|
@PostMapping("/dev/bts-simulator/reset")
|
public StateSnapshot reset()
|
{
|
simulator.reset();
|
candidateStore.removeAdapter(BtsDevelopmentSimulator.ADAPTER_CODE);
|
return simulator.snapshot();
|
}
|
|
/** 模拟现场人工统一开关全部检测柜和采集器,不发送任何设备控制命令。 */
|
@PostMapping("/dev/bts-simulator/power")
|
public StateSnapshot setPower(@RequestBody SimulatorPowerCommand command)
|
{
|
if (command == null || command.poweredOn() == null)
|
throw badRequest(new IllegalArgumentException("poweredOn must be true or false"));
|
simulator.setAllDevicesPoweredOn(command.poweredOn());
|
synchronizeOnlineStatusIfConfigured();
|
return simulator.snapshot();
|
}
|
|
/** 添加一台虚拟设备。 */
|
@PostMapping("/dev/bts-simulator/devices")
|
public StateSnapshot createDevice(@RequestBody CreateDeviceCommand command)
|
{
|
try
|
{
|
if (command.model() == null || command.model().isBlank())
|
simulator.addDevice(command.ip(), command.channelCount());
|
else
|
simulator.addNewareDevice(command.ip(), command.model());
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 修改指定虚拟设备的地址、通道数和在线状态。 */
|
@PutMapping("/dev/bts-simulator/devices/{deviceId}")
|
public StateSnapshot updateDevice(@PathVariable int deviceId, @RequestBody DeviceStateCommand command)
|
{
|
try
|
{
|
simulator.updateDevice(deviceId, command.ip(), command.channelCount(), command.online());
|
if (command.model() != null && !command.model().isBlank())
|
simulator.configureNewareProfile(deviceId, command.model());
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 删除指定虚拟设备。 */
|
@DeleteMapping("/dev/bts-simulator/devices/{deviceId}")
|
public StateSnapshot deleteDevice(@PathVariable int deviceId)
|
{
|
try
|
{
|
simulator.deleteDevice(deviceId);
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 添加一台与检测柜分开配置的虚拟采集器。 */
|
@PostMapping("/dev/bts-simulator/collectors")
|
public StateSnapshot createCollector(@RequestBody CreateCollectorCommand command)
|
{
|
try
|
{
|
simulator.addCollector(command.ip(), command.channelCount());
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 修改虚拟采集器的地址、通道数和在线状态。 */
|
@PutMapping("/dev/bts-simulator/collectors/{collectorId}")
|
public StateSnapshot updateCollector(@PathVariable int collectorId,
|
@RequestBody CollectorStateCommand command)
|
{
|
try
|
{
|
simulator.updateCollector(collectorId, command.ip(), command.channelCount(), command.online());
|
synchronizeOnlineStatusIfConfigured();
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 配置采集器采样频率和可重复的历史下载故障场景。 */
|
@PutMapping("/dev/bts-simulator/collectors/{collectorId}/sampling")
|
public StateSnapshot configureCollectorSampling(@PathVariable int collectorId,
|
@RequestBody CollectorSamplingCommand command)
|
{
|
try
|
{
|
simulator.configureCollectorSampling(collectorId, command.sampleIntervalMs(),
|
new CollectorHistoryFaults(command.missingSequences(), command.duplicateSequence(),
|
command.conflictingSequence(), command.outOfOrder()));
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 配置稳定 S 通道的固定和动态测量项。 */
|
@PutMapping("/dev/bts-simulator/collectors/{collectorId}/channels/{channelCode}")
|
public StateSnapshot configureCollectorChannel(@PathVariable int collectorId,
|
@PathVariable String channelCode, @RequestBody CollectorChannelCommand command)
|
{
|
try
|
{
|
List<CollectorMetricState> metrics = command.metrics().stream()
|
.map(metric -> new CollectorMetricState(metric.metricCode(), metric.value(), metric.unit()))
|
.toList();
|
simulator.configureCollectorChannel(collectorId, channelCode, metrics);
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException | NullPointerException exception)
|
{
|
throw badRequest(new IllegalArgumentException("Invalid collector channel metrics", exception));
|
}
|
}
|
|
/** 查询采集器最新实时快照;离线时保留最后观测并明确返回离线事实。 */
|
@GetMapping("/dev/bts-simulator/collectors/{collectorId}/realtime")
|
public CollectorRealtimeSnapshot collectorRealtime(@PathVariable int collectorId)
|
{
|
try
|
{
|
return simulator.collectorRealtime(collectorId);
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 按偏移量分页下载采集器模拟历史,每页最多 1000 条。 */
|
@GetMapping("/dev/bts-simulator/collectors/{collectorId}/history")
|
public CollectorHistoryPage collectorHistory(@PathVariable int collectorId,
|
@RequestParam(defaultValue = "0") long offset, @RequestParam(defaultValue = "100") int limit)
|
{
|
try
|
{
|
return simulator.collectorHistory(collectorId, offset, limit);
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 删除指定虚拟采集器。 */
|
@DeleteMapping("/dev/bts-simulator/collectors/{collectorId}")
|
public StateSnapshot deleteCollector(@PathVariable int collectorId)
|
{
|
try
|
{
|
simulator.deleteCollector(collectorId);
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw badRequest(exception);
|
}
|
}
|
|
/** 修改指定虚拟设备通道的在线和工作状态。 */
|
@PutMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}")
|
public StateSnapshot updateChannel(@PathVariable int deviceId, @PathVariable int channelId,
|
@RequestBody ChannelStateCommand command)
|
{
|
try
|
{
|
simulator.updateChannel(deviceId, channelId, command.online(), command.workStatus());
|
return simulator.snapshot();
|
}
|
catch (IllegalArgumentException exception)
|
{
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, exception.getMessage(), exception);
|
}
|
}
|
|
/** 返回检测柜通道播放、record、step 和 cycle 的当前快照。 */
|
@GetMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback")
|
public PlaybackSnapshot btsPlayback(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@GetMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/records")
|
public List<com.battery.device.simulator.BtsSimulatorStateStore.BtsHistoryRecord> btsRecords(
|
@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { return simulator.btsRecords(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@GetMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/steps")
|
public List<BtsDevelopmentSimulator.BtsStepRecord> btsSteps(
|
@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { return simulator.btsSteps(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@GetMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/cycles")
|
public List<BtsDevelopmentSimulator.BtsCycleRecord> btsCycles(
|
@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { return simulator.btsCycles(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
/** 设置检测柜通道演示倍速。 */
|
@PutMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback")
|
public PlaybackSnapshot configureBtsPlayback(@PathVariable int deviceId, @PathVariable int channelId,
|
@RequestBody PlaybackCommand command)
|
{
|
try
|
{
|
if (command.speedMultiplier() != null) simulator.setBtsSpeed(deviceId, channelId, command.speedMultiplier());
|
if (command.playing() != null)
|
{
|
if (command.playing()) simulator.playBts(deviceId, channelId);
|
else simulator.pauseBts(deviceId, channelId);
|
}
|
return simulator.btsPlayback(deviceId, channelId);
|
}
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@PostMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback/play")
|
public PlaybackSnapshot playBts(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { simulator.playBts(deviceId, channelId); return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@PostMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback/pause")
|
public PlaybackSnapshot pauseBts(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { simulator.pauseBts(deviceId, channelId); return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@PostMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback/resume")
|
public PlaybackSnapshot resumeBts(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { simulator.resumeBts(deviceId, channelId); return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@PostMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback/next-step")
|
public PlaybackSnapshot nextBtsStep(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { simulator.nextBtsStep(deviceId, channelId); return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@PostMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback/next-cycle")
|
public PlaybackSnapshot nextBtsCycle(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { simulator.nextBtsCycle(deviceId, channelId); return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
@PostMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/playback/reset")
|
public PlaybackSnapshot resetBts(@PathVariable int deviceId, @PathVariable int channelId)
|
{
|
try { simulator.resetBts(deviceId, channelId); return simulator.btsPlayback(deviceId, channelId); }
|
catch (IllegalArgumentException exception) { throw badRequest(exception); }
|
}
|
|
private ResponseStatusException badRequest(IllegalArgumentException exception)
|
{
|
return new ResponseStatusException(HttpStatus.BAD_REQUEST, exception.getMessage(), exception);
|
}
|
|
private void synchronizeOnlineStatusIfConfigured()
|
{
|
if (statusSynchronizer != null) statusSynchronizer.synchronizeOnlineStatus();
|
}
|
|
public record CreateDeviceCommand(String ip, int channelCount, String model)
|
{
|
public CreateDeviceCommand(String ip, int channelCount)
|
{
|
this(ip, channelCount, null);
|
}
|
}
|
|
public record SimulatorPowerCommand(Boolean poweredOn) { }
|
|
public record DeviceStateCommand(String ip, int channelCount, boolean online, String model)
|
{
|
public DeviceStateCommand(String ip, int channelCount, boolean online)
|
{
|
this(ip, channelCount, online, null);
|
}
|
}
|
|
public record ChannelStateCommand(boolean online, DeviceWorkStatus workStatus) { }
|
|
public record CreateCollectorCommand(String ip, int channelCount) { }
|
|
public record CollectorStateCommand(String ip, int channelCount, boolean online) { }
|
|
public record CollectorSamplingCommand(int sampleIntervalMs, List<Long> missingSequences,
|
Long duplicateSequence, Long conflictingSequence, boolean outOfOrder)
|
{
|
public CollectorSamplingCommand
|
{
|
missingSequences = missingSequences == null ? List.of() : List.copyOf(missingSequences);
|
}
|
}
|
|
public record CollectorChannelCommand(List<CollectorMetricCommand> metrics) { }
|
|
public record CollectorMetricCommand(String metricCode, BigDecimal value, String unit) { }
|
|
public record PlaybackCommand(Integer speedMultiplier, Boolean playing) { }
|
}
|