From 6b99cc391064cb38c706979fafcecb9f50ea1587 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 11 十月 2026 02:07:44 +0800
Subject: [PATCH] 新威直连补上工步层与循环汇总(downloadStepLayer 实机验证后落地)
---
battery-device-bts/src/test/java/com/battery/device/bts/NewareBtsXmlCodecTest.java | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 106 insertions(+), 0 deletions(-)
diff --git a/battery-device-bts/src/test/java/com/battery/device/bts/NewareBtsXmlCodecTest.java b/battery-device-bts/src/test/java/com/battery/device/bts/NewareBtsXmlCodecTest.java
index 9f007be..5e30636 100644
--- a/battery-device-bts/src/test/java/com/battery/device/bts/NewareBtsXmlCodecTest.java
+++ b/battery-device-bts/src/test/java/com/battery/device/bts/NewareBtsXmlCodecTest.java
@@ -12,6 +12,7 @@
import java.util.Set;
import org.junit.jupiter.api.Test;
import com.battery.device.api.TestDataObservationAdapter.CabinetRealtime;
+import com.battery.device.api.TestDataObservationAdapter.StepRecord;
import com.battery.device.api.model.ChannelIdentity;
import com.battery.device.api.model.DeviceWorkStatus;
import com.battery.device.api.model.RealtimeSnapshot;
@@ -237,6 +238,111 @@
}
@Test
+ void buildsTheStepLayerRequestWithoutTheOptionalElements()
+ {
+ String request = codec.downloadStepLayerRequest(CHANNEL);
+
+ assertTrue(request.contains("<cmd>downloadStepLayer</cmd>"));
+ assertTrue(request.contains("devtype=\"25\" devid=\"81\" subdevid=\"1\" chlid=\"2\""));
+ assertTrue(request.contains("testid=\"0\""));
+ assertTrue(request.contains("dcir=\"0\""));
+ }
+
+ @Test
+ void parsesRealStepLayerWithNormalisedCycleAndDerivedStartTime()
+ {
+ // 现场实测原文(`26.117.165.166` 的 `25-81-1-1`,测试 316)。三处要点都在这个夹具里:
+ // cycleid 是 1 起要减一;只有 endatime,开始时间由 endatime − steptime 还原;
+ // stepindex 才是身份(stepid 在循环里会重复)。
+ String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><bts version=\"1.0\">"
+ + "<cmd>downloadStepLayer_resp</cmd>"
+ + "<downloadStepLayer devtype=\"25\" devid=\"81\" subdevid=\"1\" chlid=\"1\" testid=\"316\" />"
+ + "<list count=\"4\">"
+ + "<data startseqid=\"1\" endseqid=\"2\" stepindex=\"1\" stepid=\"1\" cycleid=\"1\" steptype=\"rest\""
+ + " steptime=\"23100\" endatime=\"2026-10-10 09:09:14\" startvolt=\"0.806611083984375\""
+ + " endvolt=\"0.80638037109375\" startcurr=\"0\" endcurr=\"0\" cap=\"0\" eng=\"0\" dcir=\"0\" />"
+ + "<data startseqid=\"3\" endseqid=\"304\" stepindex=\"2\" stepid=\"2\" cycleid=\"1\" steptype=\"cc\""
+ + " steptime=\"18000000\" endatime=\"2026-10-10 14:09:14\" startvolt=\"1.07542861328125\""
+ + " endvolt=\"1.972108203125\" startcurr=\"0.563903076171875\" endcurr=\"0.563332702636719\""
+ + " cap=\"2.81630277777778\" eng=\"5.58447055555556\" dcir=\"0\" />"
+ + "<data startseqid=\"305\" endseqid=\"307\" stepindex=\"3\" stepid=\"3\" cycleid=\"1\" steptype=\"rest\""
+ + " steptime=\"83600\" endatime=\"2026-10-10 14:10:38\" startvolt=\"1.7850955078125\""
+ + " endvolt=\"1.42794287109375\" startcurr=\"0\" endcurr=\"0\" cap=\"0\" eng=\"0\" dcir=\"0\" />"
+ + "<data startseqid=\"308\" endseqid=\"309\" stepindex=\"4\" stepid=\"4\" cycleid=\"1\" steptype=\"dc\""
+ + " steptime=\"27600\" endatime=\"2026-10-10 14:11:05\" startvolt=\"1.27452001953125\""
+ + " endvolt=\"0.49604326171875\" startcurr=\"-0.562971618652344\" endcurr=\"-0.5622275390625\""
+ + " cap=\"0.00431017035590278\" eng=\"0.00426197048611111\" dcir=\"0\" /></list></bts>";
+
+ List<StepRecord> steps = codec.parseStepLayer(xml, Instant.parse("2026-10-10T14:12:00Z"));
+
+ assertEquals(4, steps.size());
+ StepRecord first = steps.get(0);
+ assertEquals(0, first.cycleNo());
+ assertEquals(1, first.stepNo());
+ assertEquals("rest", first.stepType());
+ assertEquals("搁置", first.stepName());
+ assertEquals("2026-10-10T01:09:14Z", first.endedAt().toString());
+ // 开始时间 = 结束时间 − 23100 ms;与该测试首行 atime(09:08:51 设备本地)对得上。
+ assertEquals("2026-10-10T01:08:50.900Z", first.startedAt().toString());
+ assertEquals("neware:316:step:1", first.sourceRecordId());
+ assertEquals("Asia/Hong_Kong", first.timezoneId());
+ assertEquals("2026-10-10 09:09:14", first.deviceTimeRaw());
+
+ StepRecord charge = steps.get(1);
+ assertEquals("充电", charge.stepName());
+ assertEquals(2.81630277777778, charge.capacityAh(), 1e-12);
+ assertEquals(5.58447055555556, charge.energyWh(), 1e-12);
+ assertEquals(1.972108203125, charge.endVoltageV(), 1e-12);
+ assertEquals(0.563332702636719, charge.endCurrentA(), 1e-12);
+ assertEquals(3, charge.measurements().get("startSeqId").intValue());
+ assertEquals(304, charge.measurements().get("endSeqId").intValue());
+
+ StepRecord discharge = steps.get(3);
+ assertEquals("放电", discharge.stepName());
+ assertEquals(0.00431017035590278, discharge.capacityAh(), 1e-15);
+ }
+
+ @Test
+ void leavesStepCapacityEmptyWhenTheDirectionIsUnknown()
+ {
+ // 方向未知时不给单项容量能量:cap 是单一值,方向没确认就不知道它属于哪一侧(ADR-0069 §3)。
+ String xml = "<bts><cmd>downloadStepLayer_resp</cmd>"
+ + "<downloadStepLayer devtype=\"25\" devid=\"81\" subdevid=\"1\" chlid=\"1\" testid=\"9\" />"
+ + "<list count=\"1\"><data startseqid=\"1\" endseqid=\"9\" stepindex=\"1\" stepid=\"1\" cycleid=\"2\""
+ + " steptype=\"--\" steptime=\"60000\" endatime=\"2026-10-10 09:09:14\" startvolt=\"1\" endvolt=\"1\""
+ + " startcurr=\"0\" endcurr=\"0\" cap=\"1.5\" eng=\"2.5\" dcir=\"0\" /></list></bts>";
+
+ StepRecord step = codec.parseStepLayer(xml, Instant.EPOCH).get(0);
+
+ assertNull(step.stepName());
+ assertNull(step.capacityAh());
+ assertNull(step.energyWh());
+ assertEquals(1, step.cycleNo());
+ // 明细里保留的是设备原文事实(起始电压、首末序号),没有按方向分派的单项值。
+ assertEquals(1.0, step.measurements().get("startVoltage").doubleValue(), 1e-9);
+ assertEquals(9, step.measurements().get("endSeqId").intValue());
+ }
+
+ @Test
+ void reportsAnEmptyStepLayerInsteadOfFailing()
+ {
+ // 该通道的缓冲里没有测试时(实测 D 那台就是),应答是空列表而不是错误。
+ List<StepRecord> steps = codec.parseStepLayer(
+ "<bts><cmd>downloadStepLayer_resp</cmd><list count=\"0\" /></bts>", Instant.EPOCH);
+
+ assertTrue(steps.isEmpty());
+ }
+
+ @Test
+ void rejectsAStepLayerResponseToAnotherCommand()
+ {
+ BtsProtocolException exception = assertThrows(BtsProtocolException.class,
+ () -> codec.parseStepLayer("<bts><cmd>inquire_resp</cmd><list count=\"0\" /></bts>", Instant.EPOCH));
+
+ assertEquals(BtsProtocolException.Reason.UNEXPECTED_COMMAND, exception.reason());
+ }
+
+ @Test
void keepsOnlyAttributesOutsideTheKnownFieldSetAsDynamicMeasurements()
{
// 字段集随客户端构建不同(8.0.0.484 有 barcode/auxtemp,8.0.0.530 没有,实测三种构建互不相同)。
--
Gitblit v1.7.1