yeg
2026-09-05 bd50f1b756c1da2dbf92f6541407ad40f455e24a
battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
@@ -12,6 +12,14 @@
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;
@@ -35,7 +43,7 @@
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;
@@ -58,6 +66,71 @@
    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
@@ -721,7 +794,10 @@
                    .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();
    }
@@ -1203,10 +1279,17 @@
    }
    @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) { }