diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java new file mode 100644 index 00000000..68a1c18b --- /dev/null +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java @@ -0,0 +1,34 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; +import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; + +import java.nio.ByteBuffer; + +/** + * 广东燃料电池规范 §7.2.3.7 表 18:空调数据。typeCode {@code 0x33}。 + * + *

固定 5 字节: + *

+ *   status(1)                        0 关闭 / 1 启动
+ *   powerKw(2, offset -5, 1 kw, range -5~+30)
+ *   compressorInputVoltageV(2, 0.1V, 0~1000V)
+ * 
+ */ +public final class GdFcAirConditionerBlockParser implements InfoBlockParser { + + @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } + @Override public int typeCode() { return 0x33; } + @Override public int fixedLength() { return 5; } + + @Override + public InfoBlock parse(ByteBuffer buf) { + Integer status = ValueDecoder.u8raw(buf); + int rawPower = buf.getShort() & 0xFFFF; + Integer power = (rawPower == 0xFFFE || rawPower == 0xFFFF) ? null : rawPower - 5; + Double inputV = ValueDecoder.scaledU16(buf, 0.1); + return new InfoBlock.GdFcAirConditioner(status, power, inputV); + } +} diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java new file mode 100644 index 00000000..09dd0fd8 --- /dev/null +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java @@ -0,0 +1,61 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; +import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * 广东燃料电池规范 §7.2.3.5 表 15/16:辅助系统数据。typeCode {@code 0x31}。变长。 + * + *
+ *   subSystemCount(1)
+ *   For each:
+ *     airCompressorMotorVoltageV(2, 0.1V, 0~1000V)
+ *     airCompressorPowerKw(2, offset -5, 1 kw, range -5~+30)
+ *     hydrogenPumpMotorVoltageV(2, 0.2V, 0~50V)
+ *     hydrogenPumpPowerKw(2, offset -5, 1 kw)
+ *     waterPumpVoltageV(2, 0.1V)
+ *     ptcVoltageV(2, 0.1V)
+ *     ptcPowerKw(2, offset -5, 1 kw)
+ *     lowVoltageBatteryVoltageV(2, 0.1V, 0~32V)
+ * 
+ * + * 每子系统 16 字节。 + */ +public final class GdFcAuxiliaryBlockParser implements InfoBlockParser { + + @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } + @Override public int typeCode() { return 0x31; } + @Override public int fixedLength() { return -1; } + + @Override + public InfoBlock parse(ByteBuffer buf) { + int count = buf.get() & 0xFF; + List subs = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + Double airCompV = ValueDecoder.scaledU16(buf, 0.1); + Integer airCompP = readPowerOffsetMinus5(buf); + Double h2PumpV = ValueDecoder.scaledU16(buf, 0.2); + Integer h2PumpP = readPowerOffsetMinus5(buf); + Double waterPumpV = ValueDecoder.scaledU16(buf, 0.1); + Double ptcV = ValueDecoder.scaledU16(buf, 0.1); + Integer ptcP = readPowerOffsetMinus5(buf); + Double lvBattV = ValueDecoder.scaledU16(buf, 0.1); + subs.add(new InfoBlock.GdFcAuxiliary.Subsystem( + airCompV, airCompP, h2PumpV, h2PumpP, waterPumpV, ptcV, ptcP, lvBattV)); + } + return new InfoBlock.GdFcAuxiliary(count, subs); + } + + /** 功率字段:raw(2B) - 5,0xFFFE/0xFFFF 异常/无效。 */ + private static Integer readPowerOffsetMinus5(ByteBuffer buf) { + int raw = buf.getShort() & 0xFFFF; + if (raw == 0xFFFE || raw == 0xFFFF) return null; + return raw - 5; + } +} diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java new file mode 100644 index 00000000..3d841392 --- /dev/null +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java @@ -0,0 +1,37 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; +import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; + +import java.nio.ByteBuffer; + +/** + * 广东燃料电池规范 §7.2.3.6 表 17:DC/DC(燃料电池系统)数据。typeCode {@code 0x32}。 + * + *

固定 9 字节: + *

+ *   inputVoltageV(2, 0.1V, 0~400V)
+ *   inputCurrentA(2, 0.1A, 0~600A)
+ *   outputVoltageV(2, 0.1V, 0~700V)
+ *   outputCurrentA(2, 0.1A, 0~600A)
+ *   controllerTempC(1, -40 offset)
+ * 
+ */ +public final class GdFcDcDcBlockParser implements InfoBlockParser { + + @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } + @Override public int typeCode() { return 0x32; } + @Override public int fixedLength() { return 9; } + + @Override + public InfoBlock parse(ByteBuffer buf) { + Double inV = ValueDecoder.scaledU16(buf, 0.1); + Double inC = ValueDecoder.scaledU16(buf, 0.1); + Double outV = ValueDecoder.scaledU16(buf, 0.1); + Double outC = ValueDecoder.scaledU16(buf, 0.1); + Integer ctrlTemp = ValueDecoder.tempOffset40(buf); + return new InfoBlock.GdFcDcDc(inV, inC, outV, outC, ctrlTemp); + } +} diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java new file mode 100644 index 00000000..bb5a7655 --- /dev/null +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java @@ -0,0 +1,47 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; +import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; + +import java.nio.ByteBuffer; + +/** + * 广东燃料电池规范 §7.2.3.14 表 27:燃料电池汽车示范应用数据扩展。typeCode {@code 0x80}。 + * + *

结构(typeCode 由外层消费): + *

+ *   dataLength (2B WORD) = 9     表示后续扩展数据长度
+ *   stackTempC (1B, -40 offset, -40~+210°C)
+ *   airCompressorVoltageV (2B, 0.1V, 0~2000V)
+ *   airCompressorCurrentA (2B, 0.1A, -1000 offset, -1000~+1000A)
+ *   hydrogenPumpVoltageV (2B, 0.1V, 0~2000V)
+ *   hydrogenPumpCurrentA (2B, 0.1A, -1000 offset, -1000~+1000A)
+ * 
+ * + * 含 length 字段共 11 字节 body(不含外层 1B typeCode)。 + */ +public final class GdFcDemoExtensionBlockParser implements InfoBlockParser { + + @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } + @Override public int typeCode() { return 0x80; } + @Override public int fixedLength() { return 11; } + + @Override + public InfoBlock parse(ByteBuffer buf) { + int declaredLen = buf.getShort() & 0xFFFF; + // declaredLen 通常 = 9,但即便不是也按 9 字节固定布局解,避免无谓的失败。 + Integer stackTemp = ValueDecoder.tempOffset40(buf); + Double acV = ValueDecoder.scaledU16(buf, 0.1); + Double acC = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); + Double h2V = ValueDecoder.scaledU16(buf, 0.1); + Double h2C = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); + // 防御:若 declaredLen 与实际不一致,仅在 DEBUG 留痕。 + if (declaredLen != 9 && org.slf4j.LoggerFactory.getLogger(GdFcDemoExtensionBlockParser.class).isDebugEnabled()) { + org.slf4j.LoggerFactory.getLogger(GdFcDemoExtensionBlockParser.class) + .debug("[gb32960/gd-fc/0x80] declaredLen={} (expected 9), parsed by fixed layout", declaredLen); + } + return new InfoBlock.GdFcDemoExtension(stackTemp, acV, acC, h2V, h2C); + } +} diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java new file mode 100644 index 00000000..7c8caaf5 --- /dev/null +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java @@ -0,0 +1,75 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; +import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * 广东燃料电池汽车示范应用城市群综合监管平台 §7.2.3.4 表 13/14:燃料电池电堆数据。 + * + *

typeCode {@code 0x30},工程上观察到对端在 GB/T 32960.3-2016 帧({@code 2323} + * 起始符)里下发该块。本规范在 GB/T 32960.3-2016 标准里属于 {@code 0x30~0x7F} 预留区段, + * 因此只有当对端被 vendor profile 选中后才启用此 parser。 + * + *

结构: + *

+ *   stackCount (1B)
+ *   For each stack:
+ *     engineWorkState (1B)                         00 关闭 / 01 打开 / FE 异常 / FF 无效
+ *     stackWaterOutletTempC (1B, -40 offset)
+ *     hydrogenInletPressureKpa (2B WORD, -100 offset, 0.1 kPa)
+ *     airInletPressureKpa (2B WORD, -100 offset, 0.1 kPa)
+ *     airInletTempC (1B, -40 offset)
+ *     maxCellVoltageId (2B WORD)
+ *     minCellVoltageId (2B WORD)
+ *     maxCellVoltageV (2B WORD, 0.001 V)
+ *     minCellVoltageV (2B WORD, 0.001 V)
+ *     avgCellVoltageV (2B WORD, 0.001 V)
+ *     cellCount (2B WORD)
+ *     frameCellStart (2B WORD)
+ *     frameCellCount (1B)
+ *     frameCellVoltagesV (2 * frameCellCount, 0.001 V)
+ * 
+ */ +public final class GdFcStackBlockParser implements InfoBlockParser { + + @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } + @Override public int typeCode() { return 0x30; } + @Override public int fixedLength() { return -1; } + + @Override + public InfoBlock parse(ByteBuffer buf) { + int stackCount = buf.get() & 0xFF; + List stacks = new ArrayList<>(stackCount); + for (int i = 0; i < stackCount; i++) { + Integer workState = ValueDecoder.u8raw(buf); + Integer waterTemp = ValueDecoder.tempOffset40(buf); + Double h2InletP = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); + Double airInletP = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); + Integer airInletTemp = ValueDecoder.tempOffset40(buf); + Integer maxId = ValueDecoder.u16(buf); + Integer minId = ValueDecoder.u16(buf); + Double maxV = ValueDecoder.scaledU16(buf, 0.001); + Double minV = ValueDecoder.scaledU16(buf, 0.001); + Double avgV = ValueDecoder.scaledU16(buf, 0.001); + Integer cellCount = ValueDecoder.u16(buf); + Integer frameStart = ValueDecoder.u16(buf); + int frameCells = buf.get() & 0xFF; + List frameVoltages = new ArrayList<>(frameCells); + int safe = Math.min(frameCells, buf.remaining() / 2); + for (int k = 0; k < safe; k++) { + frameVoltages.add((buf.getShort() & 0xFFFF) * 0.001); + } + stacks.add(new InfoBlock.GdFcStack.Stack( + workState, waterTemp, h2InletP, airInletP, airInletTemp, + maxId, minId, maxV, minV, avgV, + cellCount, frameStart, frameCells, frameVoltages)); + } + return new InfoBlock.GdFcStack(stackCount, stacks); + } +} diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java new file mode 100644 index 00000000..955bed01 --- /dev/null +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java @@ -0,0 +1,36 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; +import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; + +import java.nio.ByteBuffer; + +/** + * 广东燃料电池规范 §7.2.3.8 表 19:车辆信息(补充)。typeCode {@code 0x34}。 + * + *

固定 7 字节: + *

+ *   collisionAlarm(1)                0 无 / 1 有
+ *   ambientTempC(2, -40 offset, 1°C)
+ *   ambientPressureKpa(2, -100 offset, 0.1 kPa)
+ *   hydrogenMassKg(2, 0.1 kg)
+ * 
+ */ +public final class GdFcVehicleInfoBlockParser implements InfoBlockParser { + + @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } + @Override public int typeCode() { return 0x34; } + @Override public int fixedLength() { return 7; } + + @Override + public InfoBlock parse(ByteBuffer buf) { + Integer collision = ValueDecoder.u8raw(buf); + int rawTemp = buf.getShort() & 0xFFFF; + Integer tempC = (rawTemp == 0xFFFE || rawTemp == 0xFFFF) ? null : rawTemp - 40; + Double pressure = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); + Double h2Mass = ValueDecoder.scaledU16(buf, 0.1); + return new InfoBlock.GdFcVehicleInfo(collision, tempC, pressure, h2Mass); + } +} diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java index 0660f48b..a36ee03a 100644 --- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java @@ -32,6 +32,12 @@ public sealed interface InfoBlock InfoBlock.FuelCellStack, InfoBlock.SuperCapacitor, InfoBlock.SuperCapacitorExtreme, + InfoBlock.GdFcStack, + InfoBlock.GdFcAuxiliary, + InfoBlock.GdFcDcDc, + InfoBlock.GdFcAirConditioner, + InfoBlock.GdFcVehicleInfo, + InfoBlock.GdFcDemoExtension, InfoBlock.Raw { InfoBlockType type(); @@ -402,6 +408,95 @@ public sealed interface InfoBlock @Override public InfoBlockType type() { return InfoBlockType.SUPER_CAPACITOR_EXTREME; } } + // ======================================================================== + // 广东燃料电池汽车示范规范扩展块(typeCode 0x30~0x34、0x80) + // ======================================================================== + + /** + * 广东规范 0x30 燃料电池电堆数据(表 13/14)。变长。 + * 一个 stackCount + 多个 stack;每个 stack 包含定长字段后跟 frameCellCount 个单体电压(2B/个)。 + */ + record GdFcStack(int stackCount, List stacks) implements InfoBlock { + @Override public InfoBlockType type() { return InfoBlockType.GD_FC_STACK; } + + public record Stack( + Integer engineWorkState, // 1B + Integer stackWaterOutletTempC, // 1B (-40 offset) + Double hydrogenInletPressureKpa, // 2B (-100 offset, 0.1 kPa) + Double airInletPressureKpa, // 2B (-100 offset, 0.1 kPa) + Integer airInletTempC, // 1B (-40 offset) + Integer maxCellVoltageId, // 2B + Integer minCellVoltageId, // 2B + Double maxCellVoltageV, // 2B (0.001 V) + Double minCellVoltageV, // 2B (0.001 V) + Double avgCellVoltageV, // 2B (0.001 V) + Integer cellCount, // 2B + Integer frameCellStart, // 2B + Integer frameCellCount, // 1B + List frameCellVoltagesV // 2*m B (0.001 V) + ) {} + } + + /** 广东规范 0x31 辅助系统数据(表 15/16)。变长。 */ + record GdFcAuxiliary(int subSystemCount, List subsystems) implements InfoBlock { + @Override public InfoBlockType type() { return InfoBlockType.GD_FC_AUXILIARY; } + + public record Subsystem( + Double airCompressorMotorVoltageV, // 2B 0.1V + Integer airCompressorPowerKw, // 2B (-5 offset, 1 kw) + Double hydrogenPumpMotorVoltageV, // 2B 0.2V + Integer hydrogenPumpPowerKw, // 2B (-5 offset, 1 kw) + Double waterPumpVoltageV, // 2B 0.1V + Double ptcVoltageV, // 2B 0.1V + Integer ptcPowerKw, // 2B (-5 offset, 1 kw) + Double lowVoltageBatteryVoltageV // 2B 0.1V + ) {} + } + + /** 广东规范 0x32 DC/DC(燃料电池系统)数据(表 17)。固定 9 字节。 */ + record GdFcDcDc( + Double inputVoltageV, // 2B 0.1V (0~400V) + Double inputCurrentA, // 2B 0.1A (0~600A) + Double outputVoltageV, // 2B 0.1V (0~700V) + Double outputCurrentA, // 2B 0.1A (0~600A) + Integer controllerTempC // 1B -40 offset + ) implements InfoBlock { + @Override public InfoBlockType type() { return InfoBlockType.GD_FC_DCDC; } + } + + /** 广东规范 0x33 空调数据(表 18)。固定 5 字节。 */ + record GdFcAirConditioner( + Integer status, // 1B 0 关闭 / 1 启动 + Integer powerKw, // 2B (-5 offset, 1 kw, range -5~+30) + Double compressorInputVoltageV // 2B 0.1V (0~1000V) + ) implements InfoBlock { + @Override public InfoBlockType type() { return InfoBlockType.GD_FC_AIR_CONDITIONER; } + } + + /** 广东规范 0x34 车辆信息数据(表 19)。固定 7 字节。 */ + record GdFcVehicleInfo( + Integer collisionAlarm, // 1B 0 无 / 1 有 + Integer ambientTempC, // 2B (-40 offset, 1°C) + Double ambientPressureKpa, // 2B (-100 offset, 0.1 kPa) + Double hydrogenMassKg // 2B 0.1 kg + ) implements InfoBlock { + @Override public InfoBlockType type() { return InfoBlockType.GD_FC_VEHICLE_INFO; } + } + + /** + * 广东规范 0x80 燃料电池汽车示范应用数据扩展(表 27)。固定 12 字节 + * (含 2 字节内层 length=9 + 9 字节数据)。 + */ + record GdFcDemoExtension( + Integer stackTempC, // 1B -40 offset + Double airCompressorVoltageV, // 2B 0.1V (0~2000V) + Double airCompressorCurrentA, // 2B 0.1A (-1000 offset) + Double hydrogenPumpVoltageV, // 2B 0.1V (0~2000V) + Double hydrogenPumpCurrentA // 2B 0.1A (-1000 offset) + ) implements InfoBlock { + @Override public InfoBlockType type() { return InfoBlockType.GD_FC_DEMO_EXTENSION; } + } + // ======================================================================== // Raw / Unknown // ======================================================================== diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java index d9d1f0b8..0f7bc1dc 100644 --- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java @@ -44,6 +44,18 @@ public enum InfoBlockType { SUPER_CAPACITOR_EXTREME, /** 自定义数据 0x80~0xFE(表 27)。 */ USER_DEFINED, + /** 广东燃料电池规范 0x30 燃料电池电堆数据(表 14)。 */ + GD_FC_STACK, + /** 广东燃料电池规范 0x31 辅助系统数据(表 16)。 */ + GD_FC_AUXILIARY, + /** 广东燃料电池规范 0x32 DC/DC 数据(表 17)。 */ + GD_FC_DCDC, + /** 广东燃料电池规范 0x33 空调数据(表 18)。 */ + GD_FC_AIR_CONDITIONER, + /** 广东燃料电池规范 0x34 车辆信息数据(表 19)。 */ + GD_FC_VEHICLE_INFO, + /** 广东燃料电池规范 0x80 燃料电池汽车示范应用数据扩展(表 27)。 */ + GD_FC_DEMO_EXTENSION, /** 原始/未解析。 */ RAW } diff --git a/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java b/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java new file mode 100644 index 00000000..f9d65acf --- /dev/null +++ b/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java @@ -0,0 +1,86 @@ +package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; + +import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; + +/** + * 单元测试:广东燃料电池规范 fixed-length parser(0x32 / 0x33 / 0x34 / 0x80)逐字段还原。 + * 0x30/0x31 是变长,集成层会在 BodyParser 全链路测试里覆盖。 + */ +class GdFcParserTest { + + @Test + void dcDcBlockParsesAllFields() { + // inV=300.0 inC=120.0 outV=560.0 outC=110.5 ctrlTemp=65°C + ByteBuffer buf = ByteBuffer.wrap(new byte[]{ + 0x0B, (byte) 0xB8, // 0x0BB8 = 3000 → 300.0 V + 0x04, (byte) 0xB0, // 0x04B0 = 1200 → 120.0 A + 0x15, (byte) 0xE0, // 0x15E0 = 5600 → 560.0 V + 0x04, 0x52, // 0x0452 = 1106 → 110.6 A + 0x69 // 0x69 = 105 - 40 = 65 °C + }); + InfoBlock.GdFcDcDc b = (InfoBlock.GdFcDcDc) new GdFcDcDcBlockParser().parse(buf); + assertThat(b.inputVoltageV()).isCloseTo(300.0, within(1e-6)); + assertThat(b.inputCurrentA()).isCloseTo(120.0, within(1e-6)); + assertThat(b.outputVoltageV()).isCloseTo(560.0, within(1e-6)); + assertThat(b.outputCurrentA()).isCloseTo(110.6, within(1e-6)); + assertThat(b.controllerTempC()).isEqualTo(65); + } + + @Test + void airConditionerParsesPowerOffsetAndStatus() { + // status=01 power raw=20 (= 15 kw after -5 offset) inputV=540.0 + ByteBuffer buf = ByteBuffer.wrap(new byte[]{ + 0x01, + 0x00, 0x14, // 20 - 5 = 15 kw + 0x15, (byte) 0x18 // 0x1518 = 5400 → 540.0 V + }); + InfoBlock.GdFcAirConditioner b = + (InfoBlock.GdFcAirConditioner) new GdFcAirConditionerBlockParser().parse(buf); + assertThat(b.status()).isEqualTo(1); + assertThat(b.powerKw()).isEqualTo(15); + assertThat(b.compressorInputVoltageV()).isCloseTo(540.0, within(1e-6)); + } + + @Test + void vehicleInfoParsesAmbientFields() { + // collision=00 ambientTemp raw=70 (=30°C) ambientPressure raw=1100 (10kPa) h2Mass raw=80 (8.0 kg) + ByteBuffer buf = ByteBuffer.wrap(new byte[]{ + 0x00, + 0x00, 0x46, // 70 - 40 = 30°C + 0x04, 0x4C, // 0x044C = 1100 → 110*0.1 - 100 = 10.0 kPa + 0x00, 0x50 // 0x0050 = 80 → 8.0 kg + }); + InfoBlock.GdFcVehicleInfo b = + (InfoBlock.GdFcVehicleInfo) new GdFcVehicleInfoBlockParser().parse(buf); + assertThat(b.collisionAlarm()).isEqualTo(0); + assertThat(b.ambientTempC()).isEqualTo(30); + assertThat(b.ambientPressureKpa()).isCloseTo(10.0, within(1e-6)); + assertThat(b.hydrogenMassKg()).isCloseTo(8.0, within(1e-6)); + } + + @Test + void demoExtensionParsesInnerLengthAndFields() { + // length=9, stackTemp=50, acV=372.0, acC=0, h2V=1.3V, h2C=0 + ByteBuffer buf = ByteBuffer.wrap(new byte[]{ + 0x00, 0x09, // declared length + 0x5A, // 90 - 40 = 50°C + 0x0E, (byte) 0x88, // 0x0E88 = 3720 → 372.0V + 0x27, 0x10, // 0x2710 = 10000 → 0 A (after -1000) + 0x00, 0x0D, // 0x000D = 13 → 1.3V + 0x27, 0x10 // 0 A + }); + InfoBlock.GdFcDemoExtension b = + (InfoBlock.GdFcDemoExtension) new GdFcDemoExtensionBlockParser().parse(buf); + assertThat(b.stackTempC()).isEqualTo(50); + assertThat(b.airCompressorVoltageV()).isCloseTo(372.0, within(1e-6)); + assertThat(b.airCompressorCurrentA()).isCloseTo(0.0, within(1e-6)); + assertThat(b.hydrogenPumpVoltageV()).isCloseTo(1.3, within(1e-6)); + assertThat(b.hydrogenPumpCurrentA()).isCloseTo(0.0, within(1e-6)); + } +} diff --git a/reference/广东燃料电池汽车示范应用城市群综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf b/reference/广东燃料电池汽车示范应用城市群综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf new file mode 100644 index 00000000..ab50e811 Binary files /dev/null and b/reference/广东燃料电池汽车示范应用城市群综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf differ