| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | @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 没有,实测三种构建互不相同)。 |