| | |
| | | import javax.xml.stream.XMLStreamConstants; |
| | | import javax.xml.stream.XMLStreamReader; |
| | | import com.battery.device.model.DeviceWorkStatus; |
| | | import com.battery.device.model.DeviceConnectionStatus; |
| | | import com.battery.device.model.DeviceIdentity; |
| | | import com.battery.device.model.DeviceObservation; |
| | | import com.battery.device.model.DetectedChannel; |
| | | import com.battery.device.model.DetectedDeviceMetadata; |
| | | import com.battery.device.model.ChannelIdentity; |
| | | import com.battery.device.model.AdapterCapabilities; |
| | | import com.battery.device.api.DeviceObservationAdapter; |
| | | import com.battery.device.simulator.BtsSimulatorStateStore; |
| | | import com.battery.device.simulator.BtsSimulatorStateStore.ChannelState; |
| | | import com.battery.device.simulator.BtsSimulatorStateStore.ChannelTombstone; |
| | |
| | | import com.battery.device.simulator.DevelopmentCollectorSimulation.CollectorRealtimeSnapshot; |
| | | |
| | | /** 提供由开发后端托管的临时只读 BTS 模拟端点。 */ |
| | | public final class BtsDevelopmentSimulator implements AutoCloseable |
| | | public final class BtsDevelopmentSimulator implements DeviceObservationAdapter |
| | | { |
| | | public static final String HOST = "127.0.0.1"; |
| | | public static final int DEFAULT_PORT = 15020; |
| | |
| | | private static final long SIMULATED_RECORD_INTERVAL_SECONDS = 60L; |
| | | private static final DateTimeFormatter BTS_HISTORY_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH.mm.ss") |
| | | .withZone(ZoneId.of("Asia/Hong_Kong")); |
| | | |
| | | @Override |
| | | public String adapterCode() |
| | | { |
| | | return ADAPTER_CODE; |
| | | } |
| | | |
| | | @Override |
| | | public AdapterCapabilities capabilities() |
| | | { |
| | | return AdapterCapabilities.observeOnly(); |
| | | } |
| | | |
| | | /** 直接提供模拟器当前拓扑,供不需要 TCP/XML 的契约测试使用。 */ |
| | | @Override |
| | | public synchronized List<DetectedChannel> discoverChannels() |
| | | { |
| | | List<DetectedChannel> result = new ArrayList<>(); |
| | | for (DeviceState device : state.devices()) |
| | | { |
| | | BtsDeviceProfile profile = profileFor(device); |
| | | for (ChannelState channel : device.channels()) |
| | | { |
| | | ChannelIdentity identity = new ChannelIdentity(ADAPTER_CODE, profile.devtype(), device.deviceId(), |
| | | profile.subDeviceId(), channel.channelId()); |
| | | result.add(new DetectedChannel(identity, device.ip(), device.online() && channel.online(), |
| | | new DetectedDeviceMetadata(profile.name(), profile.manufacturer(), profile.model(), |
| | | simulatorMac(profile.devtype(), device.deviceId())))); |
| | | } |
| | | } |
| | | return List.copyOf(result); |
| | | } |
| | | |
| | | /** 直接提供模拟器观测;离线通道不保留上一次测量或工步事实。 */ |
| | | @Override |
| | | public synchronized java.util.Map<ChannelIdentity, DeviceObservation> observe( |
| | | java.util.Set<ChannelIdentity> channels) |
| | | { |
| | | if (channels == null || channels.isEmpty()) return java.util.Map.of(); |
| | | java.util.Map<ChannelIdentity, DeviceObservation> result = new java.util.LinkedHashMap<>(); |
| | | for (ChannelIdentity requested : channels) |
| | | { |
| | | DeviceState device = state.devices().stream().filter(item -> item.deviceId() == requested.devid()) |
| | | .findFirst().orElse(null); |
| | | ChannelState channel = device == null ? null : device.channels().stream() |
| | | .filter(item -> item.channelId() == requested.chlid()).findFirst().orElse(null); |
| | | if (device == null || channel == null || !ADAPTER_CODE.equals(requested.adapterCode()) |
| | | || !device.online() || !channel.online()) |
| | | { |
| | | result.put(requested, DeviceObservation.offline(requested, "OFFLINE")); |
| | | continue; |
| | | } |
| | | BtsDeviceProfile profile = profileFor(device); |
| | | if (requested.devtype() != profile.devtype() || requested.subdevid() != profile.subDeviceId()) |
| | | { |
| | | result.put(requested, DeviceObservation.offline(requested, "OFFLINE")); |
| | | continue; |
| | | } |
| | | result.put(requested, new DeviceObservation( |
| | | new DeviceIdentity(ADAPTER_CODE, profile.devtype(), device.deviceId(), profile.subDeviceId()), |
| | | requested, DeviceConnectionStatus.ONLINE, channel.workStatus(), List.of(), null, |
| | | java.util.Map.of(), null)); |
| | | } |
| | | return java.util.Map.copyOf(result); |
| | | } |
| | | |
| | | /** 启动仅监听 IPv4 本机回环地址的开发模拟器;传入 0 时由系统分配测试端口。 */ |
| | | public BtsDevelopmentSimulator(int port) throws IOException |
| | |
| | | .append(decimal(voltage)).append("\" capacity=\"").append(decimal(capacity)) |
| | | .append("\" energy=\"").append(decimal(energy)).append("\" totaltime=\"") |
| | | .append(working ? tick : 0).append("\" relativetime=\"").append(working ? tick : 0) |
| | | .append("\" open_or_close=\"0\" log_code=\"0\"/>"); |
| | | .append("\" open_or_close=\"0\" log_code=\"0\">") |
| | | // 模拟器返回当前设备方案,供统一观测模型展示;不代表真实 BTS 的完整方案字段。 |
| | | .append("<plan><step step_no=\"").append(stepNo).append("\" step_type=\"") |
| | | .append(stepType).append("\" name=\"").append(stepType).append("\"/></plan></inquire>"); |
| | | } |
| | | return response.append("</list></bts>").toString(); |
| | | } |
| | |
| | | } |
| | | |
| | | @Override |
| | | public synchronized void close() throws Exception |
| | | public synchronized void close() |
| | | { |
| | | running = false; |
| | | simulator.close(); |
| | | try |
| | | { |
| | | simulator.close(); |
| | | } |
| | | catch (Exception exception) |
| | | { |
| | | throw new IllegalStateException("关闭开发模拟器失败", exception); |
| | | } |
| | | } |
| | | |
| | | private record ObservedChannel(int deviceId, ChannelState channel) { } |