| | |
| | | import java.time.Instant; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.context.annotation.Profile; |
| | | import com.ruoyi.device.bts.BtsDevelopmentSimulator; |
| | | |
| | | /** 按各采集器配置频率推进开发模拟测量,不参与正式测试数据持久化。 */ |
| | | @Component |
| | | @Profile("dev-simulator") |
| | | public class DevelopmentCollectorSamplingScheduler |
| | | { |
| | | private final BtsDevelopmentSimulator simulator; |
| New file |
| | |
| | | package com.ruoyi.config; |
| | | |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.context.annotation.Profile; |
| | | import com.ruoyi.system.service.ITestDataReceptionService; |
| | | |
| | | /** 开发环境按模拟器槽位主动形成完整实时采集快照;生产轮询周期由真实设备调度器决定。 */ |
| | | @Component |
| | | @Profile("dev-simulator") |
| | | public class DevelopmentRealtimeSnapshotScheduler |
| | | { |
| | | private final ITestDataReceptionService reception; |
| | | |
| | | public DevelopmentRealtimeSnapshotScheduler(ITestDataReceptionService reception) { this.reception = reception; } |
| | | |
| | | @Scheduled(fixedDelay = 100) |
| | | public void capture() { reception.captureDevelopmentRealtimeSnapshots(); } |
| | | } |
| | |
| | | |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.context.annotation.Profile; |
| | | import com.ruoyi.device.bts.BtsDevelopmentSimulator; |
| | | import com.ruoyi.device.registry.DeviceAdapterRegistry; |
| | | import com.ruoyi.system.service.impl.DeviceDiscoveryPersistenceService; |
| | | import com.ruoyi.system.service.impl.DeviceDiscoveryPersistenceService.DiscoverySource; |
| | | import com.ruoyi.system.service.ITestDataReceptionService; |
| | | |
| | | /** 轮询代码固定装配的 dev-sim;该运行时不依赖数据库适配器记录。 */ |
| | | @Component |
| | | @Profile("dev-simulator") |
| | | public class DevelopmentSimulatorDiscoveryScheduler |
| | | { |
| | | private final DeviceAdapterRegistry registry; |
| | | private final DeviceDiscoveryPersistenceService persistence; |
| | | private final ITestDataReceptionService reception; |
| | | |
| | | public DevelopmentSimulatorDiscoveryScheduler(DeviceAdapterRegistry registry, |
| | | DeviceDiscoveryPersistenceService persistence, ITestDataReceptionService reception) |
| | | DeviceDiscoveryPersistenceService persistence) |
| | | { |
| | | this.registry = registry; |
| | | this.persistence = persistence; |
| | | this.reception = reception; |
| | | } |
| | | |
| | | /** 注册表已包含固定开发适配器时,将一轮检测柜观测写入 Redis 候选。 */ |
| | |
| | | if (!registry.contains(BtsDevelopmentSimulator.ADAPTER_CODE)) return; |
| | | persistence.discoverForReception(registry, new DiscoverySource(BtsDevelopmentSimulator.ADAPTER_CODE, null, |
| | | BtsDevelopmentSimulator.ADAPTER_NAME, BtsDevelopmentSimulator.POLL_INTERVAL_MS)); |
| | | // 发现轮询成功后立即把接收窗口内的模拟 record 写入测试事实;页面不再读取模拟器历史。 |
| | | reception.captureDevelopmentCabinetRecords(); |
| | | // 采集器拥有独立采样频率;只接收本轮实时快照,不读取历史下载接口。 |
| | | reception.captureDevelopmentCollectorMeasurements(); |
| | | } |
| | | } |
| New file |
| | |
| | | -- 一次主动轮询形成的完整实时采集快照;原始 record/collector_sample 继续保留用于追溯。 |
| | | CREATE TABLE t_test_realtime_snapshot ( |
| | | snapshot_id BIGINT NOT NULL AUTO_INCREMENT, |
| | | test_id BIGINT NOT NULL, |
| | | binding_id BIGINT NOT NULL, |
| | | channel_id BIGINT NOT NULL, |
| | | collector_id BIGINT NOT NULL, |
| | | capture_slot_utc DATETIME(3) NOT NULL, |
| | | cabinet_sequence_id BIGINT NULL, |
| | | cabinet_device_time_utc DATETIME(3) NULL, |
| | | cabinet_device_time_raw VARCHAR(64) NULL, |
| | | collector_sequence_id BIGINT NULL, |
| | | collector_device_time_utc DATETIME(3) NULL, |
| | | collector_device_time_raw VARCHAR(64) NULL, |
| | | server_received_at_utc DATETIME(3) NOT NULL, |
| | | cycle_no INT NULL, |
| | | step_no INT NULL, |
| | | step_type VARCHAR(64) NULL, |
| | | step_name VARCHAR(191) NULL, |
| | | voltage_v DECIMAL(30,12) NULL, |
| | | current_a DECIMAL(30,12) NULL, |
| | | capacity_ah DECIMAL(30,12) NULL, |
| | | energy_wh DECIMAL(30,12) NULL, |
| | | temperature DECIMAL(30,12) NULL, |
| | | positive_potential DECIMAL(30,12) NULL, |
| | | negative_potential DECIMAL(30,12) NULL, |
| | | extension_measurements_json JSON NULL, |
| | | content_sha256 BINARY(32) NOT NULL, |
| | | PRIMARY KEY (snapshot_id), |
| | | UNIQUE KEY uk_test_snapshot_slot (test_id, binding_id, capture_slot_utc), |
| | | KEY idx_snapshot_test_time (test_id, capture_slot_utc), |
| | | CONSTRAINT fk_snapshot_test FOREIGN KEY (test_id) REFERENCES t_test (test_id), |
| | | CONSTRAINT fk_snapshot_binding FOREIGN KEY (binding_id) REFERENCES t_test_channel_binding (binding_id), |
| | | CONSTRAINT fk_snapshot_channel FOREIGN KEY (channel_id) REFERENCES t_detection_channel (channel_id), |
| | | CONSTRAINT fk_snapshot_collector FOREIGN KEY (collector_id) REFERENCES t_collector (collector_id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='主动轮询形成的完整实时采集快照'; |
| | |
| | | return List.copyOf(btsHistoryFor(btsKey(device, channelId))); |
| | | } |
| | | |
| | | /** 返回开发模拟器当前通道的持久化槽位间隔;仅供开发调度层使用。 */ |
| | | public synchronized long persistenceIntervalMillis(int deviceId, int channelId) |
| | | { |
| | | DeviceState device = requireDevice(deviceId); |
| | | requireChannel(device, channelId); |
| | | BtsPlaybackState playback = playbackState(btsKey(device, channelId), Instant.now()); |
| | | return Math.max(100L, 60000L / Math.max(1, playback.speedMultiplier())); |
| | | } |
| | | |
| | | /** 按实际出现的循环和工步汇总 record,不补齐缺失行。 */ |
| | | public synchronized List<BtsStepRecord> btsSteps(int deviceId, int channelId) |
| | | { |
| | |
| | | List<TTestWarningThreshold> selectTestThresholds(Long testId); |
| | | /** 查询当前正在接收的测试,供接收调度绑定测试事实。 */ |
| | | List<Long> selectReceivingTestIds(); |
| | | int insertRealtimeSnapshot(@Param("testId") Long testId, @Param("bindingId") Long bindingId, |
| | | @Param("channelId") Long channelId, @Param("collectorId") Long collectorId, |
| | | @Param("captureSlotUtc") Date captureSlotUtc, @Param("cabinetSequenceId") Long cabinetSequenceId, |
| | | @Param("cabinetDeviceTimeUtc") Date cabinetDeviceTimeUtc, @Param("cabinetDeviceTimeRaw") String cabinetDeviceTimeRaw, |
| | | @Param("collectorSequenceId") Long collectorSequenceId, @Param("collectorDeviceTimeUtc") Date collectorDeviceTimeUtc, |
| | | @Param("collectorDeviceTimeRaw") String collectorDeviceTimeRaw, @Param("serverReceivedAtUtc") Date serverReceivedAtUtc, |
| | | @Param("cycleNo") Integer cycleNo, @Param("stepNo") Integer stepNo, @Param("stepType") String stepType, |
| | | @Param("stepName") String stepName, @Param("voltageV") java.math.BigDecimal voltageV, |
| | | @Param("currentA") java.math.BigDecimal currentA, @Param("capacityAh") java.math.BigDecimal capacityAh, |
| | | @Param("energyWh") java.math.BigDecimal energyWh, @Param("temperature") java.math.BigDecimal temperature, |
| | | @Param("positivePotential") java.math.BigDecimal positivePotential, |
| | | @Param("negativePotential") java.math.BigDecimal negativePotential, |
| | | @Param("extensionMeasurementsJson") String extensionMeasurementsJson, @Param("contentSha256") byte[] contentSha256); |
| | | List<java.util.Map<String, Object>> selectPersistedRealtimeSnapshots(@Param("testId") Long testId); |
| | | List<java.util.Map<String, Object>> selectPersistedRecords(@org.apache.ibatis.annotations.Param("testId") Long testId); |
| | | List<java.util.Map<String, Object>> selectPersistedCollectorRecords(@org.apache.ibatis.annotations.Param("testId") Long testId); |
| | | List<java.util.Map<String, Object>> selectPersistedSteps(@org.apache.ibatis.annotations.Param("testId") Long testId); |
| | |
| | | /** 将设备已接收测量追加到当前测试事实。 */ |
| | | public interface ITestDataReceptionService |
| | | { |
| | | /** 按开发模拟器持久化槽位主动查询并保存完整实时采集快照。 */ |
| | | void captureDevelopmentRealtimeSnapshots(); |
| | | |
| | | /** 扫描当前接收中的开发检测柜,并把接收窗口内的新记录持久化。 */ |
| | | void captureDevelopmentCabinetRecords(); |
| | | |
| | |
| | | List<Map<String, Object>> changes = (List<Map<String, Object>>) (List<?>) JSON.parseArray(action.getChangesSummary(), Map.class); |
| | | Set<String> editable = switch (action.getDataType()) |
| | | { |
| | | case "REALTIME" -> Set.of("voltage", "current", "capacity", "energy", "metricValue"); |
| | | case "REALTIME" -> Set.of("voltage", "current", "capacity", "energy", "metricValue", |
| | | "temperature", "positivePotential", "negativePotential"); |
| | | case "STEP" -> Set.of("stepDuration", "capacity", "energy", "startVoltage", "endVoltage", |
| | | "chargePeakVoltage", "dischargeValleyVoltage"); |
| | | case "CYCLE" -> Set.of("chargeCapacity", "dischargeCapacity", "efficiency", "chargeEnergy", |
| | |
| | | { |
| | | return switch (dataType) |
| | | { |
| | | case "REALTIME" -> Set.of("voltage", "current", "capacity", "energy", "metricValue"); |
| | | case "REALTIME" -> Set.of("voltage", "current", "capacity", "energy", "metricValue", |
| | | "temperature", "positivePotential", "negativePotential"); |
| | | case "STEP" -> Set.of("stepDuration", "capacity", "energy", "startVoltage", "endVoltage", |
| | | "chargePeakVoltage", "dischargeValleyVoltage"); |
| | | case "CYCLE" -> Set.of("chargeCapacity", "dischargeCapacity", "efficiency", "chargeEnergy", |
| | |
| | | try { return new Long[] { Long.valueOf(parts[1]), Long.valueOf(parts[3]) }; } |
| | | catch (NumberFormatException exception) { return null; } |
| | | } |
| | | if ("REALTIME".equals(dataType) && parts.length == 2 && "snapshot".equals(parts[0])) |
| | | { |
| | | try { return new Long[] { Long.valueOf(parts[1]) }; } |
| | | catch (NumberFormatException exception) { return null; } |
| | | } |
| | | int expectedLength = "STEP".equals(dataType) ? 8 : "CYCLE".equals(dataType) ? 7 : 6; |
| | | int legacyLength = "STEP".equals(dataType) ? 5 : "CYCLE".equals(dataType) ? 4 : 3; |
| | | if (parts.length != expectedLength && parts.length != legacyLength) return null; |
| | |
| | | { |
| | | requireTest(testId); |
| | | List<Map<String, Object>> source = new java.util.ArrayList<>(); |
| | | List<Map<String, Object>> cabinet = mapper.selectPersistedRecords(testId); |
| | | List<Map<String, Object>> collector = mapper.selectPersistedCollectorRecords(testId); |
| | | if (cabinet != null) source.addAll(cabinet); |
| | | if (collector != null) source.addAll(collector); |
| | | List<Map<String, Object>> snapshots = mapper.selectPersistedRealtimeSnapshots(testId); |
| | | if (snapshots != null && !snapshots.isEmpty()) source.addAll(snapshots); |
| | | else { |
| | | // 迁移完成前兼容旧数据;新采集只从统一快照读取。 |
| | | List<Map<String, Object>> cabinet = mapper.selectPersistedRecords(testId); |
| | | List<Map<String, Object>> collector = mapper.selectPersistedCollectorRecords(testId); |
| | | if (cabinet != null) source.addAll(cabinet); |
| | | if (collector != null) source.addAll(collector); |
| | | } |
| | | List<Map<String, Object>> effective = applyEffectiveData(testId, "REALTIME", source); |
| | | attachCollectorStepContext(effective); |
| | | return effective; |
| | |
| | | |
| | | private String stableIdentity(String dataType, Map<String, Object> row) |
| | | { |
| | | if ("REALTIME".equals(dataType) && "SNAPSHOT".equals(String.valueOf(row.get("sourceType")))) |
| | | return String.valueOf(row.getOrDefault("stableIdentity", "snapshot:" + value(row, "snapshotId"))); |
| | | if ("REALTIME".equals(dataType) && "COLLECTOR".equals(String.valueOf(row.get("sourceType")))) |
| | | return String.join(":", "collector", identityPart(row, "collectorId"), |
| | | identityPart(row, "channelCode"), value(row, "sequenceId"), identityPart(row, "metricCode")); |
| | |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.MessageDigest; |
| | | import java.util.Date; |
| | | import java.time.Instant; |
| | | import java.util.Map; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.HexFormat; |
| | | import java.util.List; |
| | | import com.alibaba.fastjson2.JSON; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import com.ruoyi.device.bts.BtsDevelopmentSimulator; |
| | |
| | | private final TTestMapper mapper; |
| | | private final ITTestService tests; |
| | | private final BtsDevelopmentSimulator simulator; |
| | | private final Map<String, Instant> lastSnapshotAt = new java.util.concurrent.ConcurrentHashMap<>(); |
| | | private final Map<Long, Instant> lastRawCaptureAt = new java.util.concurrent.ConcurrentHashMap<>(); |
| | | |
| | | public TestDataReceptionServiceImpl(TTestMapper mapper, ITTestService tests, BtsDevelopmentSimulator simulator) |
| | | { |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void captureDevelopmentRealtimeSnapshots() |
| | | { |
| | | List<Long> testIds = mapper.selectReceivingTestIds(); |
| | | if (testIds == null) return; |
| | | Date now = new Date(); |
| | | for (Long testId : testIds) |
| | | { |
| | | TTest locked = mapper.selectTestByIdForUpdate(testId); |
| | | if (locked == null || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | TTest test = tests.selectTestById(testId); |
| | | if (test == null || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | // 完整快照与原始事实在同一接收事务内写入;原始事实按发现频率限流,避免 100ms 开发调度重复扫描历史。 |
| | | Instant lastRaw = lastRawCaptureAt.get(testId); |
| | | if (lastRaw == null || !now.toInstant().isBefore(lastRaw.plusSeconds(5))) |
| | | { |
| | | captureTest(test); |
| | | for (TTestBattery battery : test.getBatteries() == null ? List.<TTestBattery>of() : test.getBatteries()) |
| | | captureCollectorMeasurements(test, battery); |
| | | lastRawCaptureAt.put(testId, now.toInstant()); |
| | | } |
| | | for (TTestBattery battery : test.getBatteries() == null ? List.<TTestBattery>of() : test.getBatteries()) |
| | | captureSnapshot(test, battery, now); |
| | | } |
| | | } |
| | | |
| | | private void captureSnapshot(TTest test, TTestBattery battery, Date now) |
| | | { |
| | | TTestChannelBinding binding = battery.getBinding(); |
| | | if (binding == null || binding.getDevid() == null || binding.getChlid() == null || battery.getCollectorId() == null) return; |
| | | final long interval; |
| | | try { interval = simulator.persistenceIntervalMillis(binding.getDevid(), binding.getChlid()); } |
| | | catch (IllegalArgumentException ignored) { return; } |
| | | String key = test.getTestId() + ":" + binding.getBindingId(); |
| | | Instant last = lastSnapshotAt.get(key); |
| | | if (last != null && now.toInstant().isBefore(last.plusMillis(interval))) return; |
| | | List<BtsHistoryRecord> records; |
| | | try { records = simulator.btsRecords(binding.getDevid(), binding.getChlid()); } catch (IllegalArgumentException ignored) { return; } |
| | | BtsHistoryRecord cabinet = records.stream().max(java.util.Comparator.comparing(BtsHistoryRecord::deviceTime)).orElse(null); |
| | | CollectorRealtimeSnapshot collector; |
| | | try { collector = simulator.collectorRealtime(battery.getCollectorId().intValue()); } catch (IllegalArgumentException ignored) { return; } |
| | | if (cabinet == null || !collector.online()) return; |
| | | if (test.getStartedAtUtc() != null && cabinet.deviceTime().isBefore(test.getStartedAtUtc().toInstant())) return; |
| | | if (test.getUpdateTime() != null && cabinet.deviceTime().isBefore(test.getUpdateTime().toInstant())) return; |
| | | Map<String, CollectorRealtimeMeasurement> metrics = new LinkedHashMap<>(); |
| | | for (CollectorRealtimeMeasurement item : collector.measurements()) |
| | | if (battery.getCollectorChannels() != null && battery.getCollectorChannels().stream().anyMatch(mapping -> |
| | | java.util.Objects.equals(mapping.getChannelCode(), item.channelCode()) |
| | | && metricMatches(mapping.getMeasurementCode(), item.metricCode()))) |
| | | metrics.put(item.metricCode().toUpperCase(java.util.Locale.ROOT), item); |
| | | CollectorRealtimeMeasurement temp = metrics.get("TEMPERATURE"); |
| | | CollectorRealtimeMeasurement pos = metrics.get("POSITIVE_POTENTIAL"); |
| | | CollectorRealtimeMeasurement neg = metrics.get("NEGATIVE_POTENTIAL"); |
| | | if (temp == null || pos == null || neg == null) return; |
| | | if (test.getStartedAtUtc() != null && (temp.deviceTime().isBefore(test.getStartedAtUtc().toInstant()) |
| | | || pos.deviceTime().isBefore(test.getStartedAtUtc().toInstant()) |
| | | || neg.deviceTime().isBefore(test.getStartedAtUtc().toInstant()))) return; |
| | | if (test.getUpdateTime() != null && (temp.serverReceivedAt().isBefore(test.getUpdateTime().toInstant()) |
| | | || pos.serverReceivedAt().isBefore(test.getUpdateTime().toInstant()) |
| | | || neg.serverReceivedAt().isBefore(test.getUpdateTime().toInstant()))) return; |
| | | Date slot = new Date((now.getTime() / interval) * interval); |
| | | byte[] hash = snapshotHash(cabinet, temp, pos, neg, slot.toInstant()); |
| | | mapper.insertRealtimeSnapshot(test.getTestId(), binding.getBindingId(), binding.getChannelId(), battery.getCollectorId(), slot, |
| | | cabinet.sequenceId(), Date.from(cabinet.deviceTime()), cabinet.deviceTime().toString(), |
| | | temp.sequenceId(), Date.from(temp.deviceTime()), temp.deviceTimeRaw(), now, |
| | | cabinet.cycleNo(), cabinet.stepNo(), cabinet.stepType(), cabinet.stepName(), |
| | | BigDecimal.valueOf(cabinet.voltageV()), BigDecimal.valueOf(cabinet.currentA()), BigDecimal.valueOf(cabinet.capacityAh()), |
| | | BigDecimal.valueOf(cabinet.energyWh()), temp.value(), pos.value(), neg.value(), JSON.toJSONString(metrics), hash); |
| | | lastSnapshotAt.put(key, now.toInstant()); |
| | | } |
| | | |
| | | private byte[] snapshotHash(BtsHistoryRecord cabinet, CollectorRealtimeMeasurement temp, |
| | | CollectorRealtimeMeasurement pos, CollectorRealtimeMeasurement neg, Instant slot) |
| | | { |
| | | try { return MessageDigest.getInstance("SHA-256").digest((slot + "|" + cabinet.sequenceId() + "|" + cabinet.deviceTime() |
| | | + "|" + cabinet.voltageV() + "|" + cabinet.currentA() + "|" + temp.value() + "|" + pos.value() + "|" + neg.value()) |
| | | .getBytes(StandardCharsets.UTF_8)); } |
| | | catch (Exception exception) { throw new IllegalStateException("无法计算实时快照内容摘要", exception); } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void captureDevelopmentCabinetRecords() |
| | | { |
| | | List<Long> testIds = mapper.selectReceivingTestIds(); |
| | |
| | | <select id="selectReceivingTestIds" resultType="long"> |
| | | select test_id from t_test where del_flag = 0 and reception_status = 'RECEIVING' order by test_id |
| | | </select> |
| | | <insert id="insertRealtimeSnapshot"> |
| | | insert ignore into t_test_realtime_snapshot |
| | | (test_id, binding_id, channel_id, collector_id, capture_slot_utc, |
| | | cabinet_sequence_id, cabinet_device_time_utc, cabinet_device_time_raw, |
| | | collector_sequence_id, collector_device_time_utc, collector_device_time_raw, |
| | | server_received_at_utc, cycle_no, step_no, step_type, step_name, |
| | | voltage_v, current_a, capacity_ah, energy_wh, temperature, positive_potential, |
| | | negative_potential, extension_measurements_json, content_sha256) |
| | | values (#{testId}, #{bindingId}, #{channelId}, #{collectorId}, #{captureSlotUtc}, |
| | | #{cabinetSequenceId}, #{cabinetDeviceTimeUtc}, #{cabinetDeviceTimeRaw}, |
| | | #{collectorSequenceId}, #{collectorDeviceTimeUtc}, #{collectorDeviceTimeRaw}, |
| | | #{serverReceivedAtUtc}, #{cycleNo}, #{stepNo}, #{stepType}, #{stepName}, |
| | | #{voltageV}, #{currentA}, #{capacityAh}, #{energyWh}, #{temperature}, #{positivePotential}, |
| | | #{negativePotential}, #{extensionMeasurementsJson}, #{contentSha256}) |
| | | </insert> |
| | | <select id="selectPersistedRealtimeSnapshots" resultType="map"> |
| | | select concat('snapshot:', snapshot.snapshot_id) id, |
| | | concat('snapshot:', snapshot.snapshot_id) stableIdentity, |
| | | concat('RS-', snapshot.snapshot_id) recordNo, |
| | | 'SNAPSHOT' sourceType, battery.battery_no batteryNo, |
| | | snapshot.cycle_no cycleNo, snapshot.step_no stepNo, snapshot.step_name stepName, |
| | | snapshot.cabinet_sequence_id sequenceId, |
| | | snapshot.cabinet_device_time_utc recordedAt, snapshot.cabinet_device_time_utc absoluteTime, |
| | | snapshot.cabinet_device_time_utc deviceTime, snapshot.server_received_at_utc serverReceivedAt, |
| | | snapshot.snapshot_id snapshotId, |
| | | snapshot.temperature temperature, snapshot.positive_potential positivePotential, |
| | | snapshot.negative_potential negativePotential, |
| | | snapshot.voltage_v voltage, snapshot.current_a current, |
| | | snapshot.capacity_ah capacity, snapshot.energy_wh energy, |
| | | channel.adapter_id adapterId, channel.adapter_code adapterCode, |
| | | channel.devtype devtype, channel.devid deviceId, channel.subdevid subdevid, channel.chlid channelId, |
| | | collector.collector_id collectorId, collector.collector_code collectorCode, |
| | | snapshot.extension_measurements_json extensionMeasurementsJson |
| | | from t_test_realtime_snapshot snapshot |
| | | join t_test_channel_binding binding on binding.binding_id = snapshot.binding_id |
| | | join t_test_battery battery on battery.relation_id = binding.battery_relation_id and battery.del_flag = 0 |
| | | join t_detection_channel channel on channel.channel_id = snapshot.channel_id and channel.del_flag = 0 |
| | | join t_collector collector on collector.collector_id = snapshot.collector_id |
| | | where snapshot.test_id = #{testId} |
| | | order by snapshot.capture_slot_utc desc, snapshot.snapshot_id desc |
| | | </select> |
| | | <select id="selectPersistedRecords" resultType="map"> |
| | | select sample.sample_id id, |
| | | concat('RD-', channel.devid, '-', channel.chlid, '-', lpad(sample.seq_id, 6, '0')) recordNo, |