puzhibing
6 小时以前 e1c8c9d51f1c6e42c9868743f16fa8e52bb419cd
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java
@@ -3,112 +3,447 @@
import com.alibaba.fastjson.JSON;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.WireFormat;
import com.ruoyi.other.udan.model.UdanEmsDeviceState;
import com.ruoyi.other.emind.domain.EmindStation;
import com.ruoyi.other.emind.dto.EmindIncomeHistoryDto;
import com.ruoyi.other.emind.mapper.EmindStationMapper;
import com.ruoyi.other.emind.mapper.EmindStorageDashboardMapper;
import com.ruoyi.other.emind.service.EmindEnergyChainDetailService;
import com.ruoyi.other.udan.model.*;
import com.ruoyi.other.udan.util.UdanEmsKafkaUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/** 将 iems-open-v1.0.5.proto 消息解析为业务对象,并映射为待入库对象。 */
/** 将 IEmsAllDeviceState 中模型已有的 EMS 和能耗计字段转换为业务对象。 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "udan.ems.kafka", name = "enabled", havingValue = "true")
public class UdanEmsMessageProcessor {
    private final UdanEmsKafkaUtil kafkaUtil;
    private final EmindStorageDashboardMapper dashboardMapper;
    private final EmindStationMapper stationMapper;
    private final EmindEnergyChainDetailService emindEnergyChainDetailService;
    /** 注入 Kafka 消费工具。 */
    public UdanEmsMessageProcessor(UdanEmsKafkaUtil kafkaUtil) { this.kafkaUtil = kafkaUtil; }
    public UdanEmsMessageProcessor(UdanEmsKafkaUtil kafkaUtil, EmindStorageDashboardMapper dashboardMapper, EmindStationMapper stationMapper, EmindEnergyChainDetailService emindEnergyChainDetailService) {
        this.kafkaUtil = kafkaUtil;
        this.dashboardMapper = dashboardMapper;
        this.stationMapper = stationMapper;
        this.emindEnergyChainDetailService = emindEnergyChainDetailService;
    }
    /** 注册消息回调,避免消费者只消费但没有业务处理。 */
    //@PostConstruct
    public void register() { kafkaUtil.setMessageHandler(this::process); }
    @PostConstruct
    public void register() {
        kafkaUtil.setMessageHandler(this::process);
    }
    /** 解析消息、转换待入库对象并输出日志;此处暂不调用数据库。 */
    public void process(String emsSystemId, byte[] protobufBytes) {
    public void process(String id, byte[] bytes, String emsDeviceId) {
        try {
            UdanEmsDeviceState state = parse(emsSystemId, protobufBytes);
            log.info("2、UDAN EMS实际对象: {}", JSON.toJSONString(state));
            LocalDateTime now = LocalDateTime.now();
            UdanEmsDeviceState udanEmsDeviceState = parse(id, emsDeviceId, bytes);
            if(StringUtils.isNotEmpty(udanEmsDeviceState.getEmsDeviceId())) {
                log.info("2、UDAN EMS实际对象: {}", JSON.toJSONString(udanEmsDeviceState));
                List<EmindStation>  stations = stationMapper.selectRemoteActiveStations();
                EmindStation station = stations.stream().filter(s -> udanEmsDeviceState.getEmsDeviceId().equals(s.getStationCode())).findFirst().orElse(null);
                if(Objects.nonNull(station)) {
                    List<EmindIncomeHistoryDto> daily = udanEmsDeviceState.convert(station.getStationId());
                    Set<LocalDate> dateSet = daily.stream().map(EmindIncomeHistoryDto::getDate).collect(Collectors.toSet());
                    // 删除电站下该天已存在的旧数据
                    if(CollectionUtils.isNotEmpty(dateSet)) {
                        dashboardMapper.deleteByStationIdDay(station.getStationId(), dateSet);
                    }
                    if(CollectionUtils.isNotEmpty(daily)) {
                        dashboardMapper.upsertDaily(station.getStationId(), daily, now);
                    }
                }else{
                    log.error("UDAN EMS数据同步错误-电站信息不存在!");
                }
                // 保存设备其它信息
                emindEnergyChainDetailService.saveAll(udanEmsDeviceState);
            }
        } catch (IOException e) {
            log.error("UDAN EMS protobuf解析失败: emsSystemId={}, bytes={}", emsSystemId, protobufBytes == null ? 0 : protobufBytes.length, e);
            log.error("UDAN EMS protobuf解析失败: emsSystemId={}, bytes={}", id,
                    bytes == null ? 0 : bytes.length, e);
        }
    }
    /** 按顶层 IEmsAllDeviceState 字段号读取并构造真实业务对象。 */
    /** 兼容不带 Header 的直接调用。 */
    public void process(String id, byte[] bytes) {
        process(id, bytes, null);
    }
    public UdanEmsDeviceState parse(String id, String emsDeviceId, byte[] bytes) throws IOException {
        UdanEmsDeviceState state = new UdanEmsDeviceState();
        state.setEmsSystemId(id);
        state.setEmsDeviceId(emsDeviceId);
        read(bytes, (field, type, in) -> {
            if (field == 1 && type == 0) state.setTime(in.readUInt64());
            else if (type == 2 && field == 2) state.setEmsState(parseEms(in.readBytes().toByteArray()));
            else if (type == 2 && field == 4 && state.getPcsState() == null) {
                state.setPcsState(parsePcs(in.readBytes().toByteArray()));
            }
            else if (type == 2 && field == 5 && state.getBatteryStackState() == null) {
                state.setBatteryStackState(parseBatteryStack(in.readBytes().toByteArray()));
            }
            else if (type == 2 && field == 7 && state.getElectricMeterState() == null) {
                state.setElectricMeterState(parseElectricMeter(in.readBytes().toByteArray()));
            }
            else if (type == 2 && field == 8) state.setPowerMeterState(parsePowerMeter(in.readBytes().toByteArray()));
            else if (type == 2 && field == 14 && state.getTmsState() == null) {
                state.setTmsState(parseTms(in.readBytes().toByteArray()));
            }
            else if (type == 2 && field == 15 && state.getPeripheralDeviceState() == null) {
                state.setPeripheralDeviceState(parsePeripheral(in.readBytes().toByteArray()));
            }
            else skip(in, type);
        });
        return state;
    }
    /** 兼容不带 Header 的直接解析调用。 */
    public UdanEmsDeviceState parse(String id, byte[] bytes) throws IOException {
        UdanEmsDeviceState s = new UdanEmsDeviceState();
        s.setEmsSystemId(id);
        CodedInputStream in = CodedInputStream.newInstance(bytes == null ? new byte[0] : bytes);
        while (!in.isAtEnd()) {
            int tag = in.readTag(); if (tag == 0) break;
            int field = WireFormat.getTagFieldNumber(tag); int type = WireFormat.getTagWireType(tag);
            if (field == 1 && type == WireFormat.WIRETYPE_VARINT) s.setTime(in.readUInt64());
            else if (type == WireFormat.WIRETYPE_LENGTH_DELIMITED) parseChild(s, field, in.readBytes().toByteArray());
            else in.skipField(tag);
        }
        return s;
        return parse(id, null, bytes);
    }
    /** 解析顶层各子消息,字段号来自 iems-open-v1.0.5.proto。 */
    private void parseChild(UdanEmsDeviceState s, int field, byte[] data) throws IOException {
        switch (field) {
            case 2: s.setEms(parseEms(data)); break;
            case 4: s.getPcsStates().add(parsePcs(data)); break;
            case 5: s.getBatteryStackStates().add(parseStack(data)); break;
            case 6: s.getBatteryClusterStates().add(parseCluster(data)); break;
            case 7: s.getElectricMeterStates().add(parseMeter(data)); break;
            case 8: s.setPowerMeterCount(s.getPowerMeterCount() + 1); break;
            case 9: s.setAirConditionerCount(s.getAirConditionerCount() + 1); break;
            case 11: s.setFireCount(s.getFireCount() + 1); break;
            case 13: s.setJpfCount(s.getJpfCount() + 1); break;
            case 14: s.setTmsCount(s.getTmsCount() + 1); break;
            case 18: s.setDehumidifierCount(s.getDehumidifierCount() + 1); break;
            case 19: s.setUpsCount(s.getUpsCount() + 1); break;
            case 20: s.getPvStates().add(parsePv(data)); break;
            case 21: s.getChargeStationStates().add(parseChargeStation(data)); break;
            case 23: s.setExtPeripheralCount(s.getExtPeripheralCount() + 1); break;
            default: break;
        }
    }
    /** 解析 EMS 状态对象中的实际字段。 */
    private UdanEmsDeviceState.EmsState parseEms(byte[] b) throws IOException {
        UdanEmsDeviceState.EmsState x = new UdanEmsDeviceState.EmsState();
        read(b, (f, t, in) -> { if (t == 2 && f == 1) x.setDeviceId(in.readString()); else if (t == 5 && f == 18) x.setDemandPower(in.readFloat()); else if (t == 2 && f == 21) x.setProtoProtocolVersion(in.readString()); else if (t == 5 && f == 33) x.setDailyChargeEnergyKwh(in.readFloat()); else if (t == 5 && f == 34) x.setDailyDischargeEnergyKwh(in.readFloat()); else if (t == 1 && f == 37) x.setTotalChargeEnergyKwh(in.readDouble()); else if (t == 1 && f == 38) x.setTotalDischargeEnergyKwh(in.readDouble()); else if (t == 5 && f == 43) x.setSumsSoc(in.readFloat()); else if (t == 5 && f == 69) x.setSumsSoh(in.readFloat()); else if (t == 5 && f == 41) x.setDcBusVoltage(in.readFloat()); else if (t == 5 && f == 42) x.setDcBusCurrent(in.readFloat()); else skip(in, t); });
    private EmsState parseEms(byte[] bytes) throws IOException {
        EmsState x = new EmsState();
        read(bytes, (f, t, in) -> {
            switch (f) {
                case 1: x.setDeviceId(str(in,t)); break; case 9: x.setEmsStatus(enumName(in,t,"RunStatus")); break;
                case 18: x.setDemandPower(f32(in,t)); break; case 20: x.setTime(u64(in,t)); break; case 21: x.setProtoProtocolVersion(str(in,t)); break;
                case 22: x.setEmsRunStatus(enumAt(EmsState.EmsRunStatus.values(),in,t)); break; case 23: x.setEmsWorkStatus(enumAt(EmsState.EmsWorkStatus.values(),in,t)); break;
                case 24: x.setFaultStatus(enumAt(EmsState.ErrorStatus.values(),in,t)); break; case 25: x.setOnGridStatus(enumName(in,t,"GridStatus")); break;
                case 26: x.setControlSrc(enumAt(EmsState.ControlSrc.values(),in,t)); break; case 27: x.setEmuModeCode(enumAt(EmsState.EmuModeCode.values(),in,t)); break;
                case 28: x.setEmuAutoMod(enumAt(EmsState.EmuAutoMode.values(),in,t)); break; case 29: x.setOverDischargeProtect(enumAt(EmsState.YesNo.values(),in,t)); break;
                case 30: x.setOverChargeProtect(enumAt(EmsState.YesNo.values(),in,t)); break; case 31: x.setDemandControl(enumAt(EmsState.YesNo.values(),in,t)); break; case 32: x.setCapacityExpansionModeCtrl(enumAt(EmsState.YesNo.values(),in,t)); break;
                case 33: x.setDailyChargeEnergyKwh(f32(in,t)); break; case 34: x.setDailyDischargeEnergyKwh(f32(in,t)); break; case 35: x.setMonthlyChargeEnergyKwh(f32(in,t)); break; case 36: x.setMonthlyDischargeEnergyKwh(f32(in,t)); break;
                case 37: x.setTotalChargeEnergyKwh(f64(in,t)); break; case 38: x.setTotalDischargeEnergyKwh(f64(in,t)); break; case 39: x.setPcsSumsActivePower(f32(in,t)); break; case 40: x.setPcsSumsApparentPower(f32(in,t)); break;
                case 41: x.setDcBusVoltage(f32(in,t)); break; case 42: x.setDcBusCurrent(f32(in,t)); break; case 43: x.setSumsSoc(f32(in,t)); break; case 44: x.setCellSums(u32(in,t)); break;
                case 45: x.setBatteryAverageTempr(f32(in,t)); break; case 46: x.setBatteryMaxTemprDiff(f32(in,t)); break; case 47: x.setMaxCellVoltage(u32(in,t)); break; case 48: x.setMinCellVoltage(u32(in,t)); break;
                case 49: x.setSystemMaxAllowChgPower(f32(in,t)); break; case 50: x.setSystemMaxAllowDisPower(f32(in,t)); break; case 51: x.setRechargeableKwh(f32(in,t)); break; case 52: x.setDischargeableKwh(f32(in,t)); break;
                case 53: x.setCurrentMaxAllowChgPower(f32(in,t)); break; case 54: x.setCurrentMaxAllowDisPower(f32(in,t)); break; case 55: x.setSocUpperLimit(f32(in,t)); break; case 56: x.setSocLowerLimit(f32(in,t)); break;
                case 57: x.setMaxPowerDischargeDuration(u32(in,t)); break; case 58: x.setMaxPowerChargeDuration(u32(in,t)); break; case 60: x.setMasterCommStatus(enumName(in,t,"CommStatus")); break;
                case 61: x.setGridImportActiveEnergyKwh(f64(in,t)); break; case 62: x.setGridExportActiveEnergyKwh(f64(in,t)); break; case 63: x.setDcTotalPower(f32(in,t)); break; case 64: x.setLoadTotalPower(f32(in,t)); break;
                case 65: x.setEnergyStoreTotalPower(f32(in,t)); break; case 66: x.setHighSideGridPower(f32(in,t)); break; case 67: x.setLowSideGridPower(f32(in,t)); break; case 68: x.setPvPower(f32(in,t)); break; case 69: x.setSumsSoh(f32(in,t)); break;
                default: skip(in,t);
            }
        });
        return x;
    }
    /** 解析 PCS 实际字段。 */
    private UdanEmsDeviceState.PcsState parsePcs(byte[] b) throws IOException {
        UdanEmsDeviceState.PcsState x = new UdanEmsDeviceState.PcsState();
        read(b, (f, t, in) -> { if (t == 2 && f == 2) x.setDeviceId(in.readString()); else if (t == 5 && f == 13) x.setAcTotalActivePower(in.readFloat()); else if (t == 5 && f == 21) x.setAcTotalReactivePower(in.readFloat()); else if (t == 5 && f == 22) x.setAcTotalApparentPower(in.readFloat()); else if (t == 5 && f == 23) x.setAcTotalPowerFactor(in.readFloat()); else if (t == 5 && f == 17) x.setDailyChargeEnergyKwh(in.readFloat()); else if (t == 5 && f == 18) x.setDailyDischargeEnergyKwh(in.readFloat()); else if (t == 1 && f == 19) x.setTotalChargeEnergyKwh(in.readDouble()); else if (t == 1 && f == 20) x.setTotalDischargeEnergyKwh(in.readDouble()); else if (t == 5 && f == 59) x.setAmbientTemp(in.readFloat()); else skip(in, t); });
        return x;
    private PowerMeterState parsePowerMeter(byte[] bytes) throws IOException {
        PowerMeterState value = new PowerMeterState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 1: value.setCumulativeImportAmountPeak(f64(input, type)); break;
                case 2: value.setCumulativeImportAmountShoulder(f64(input, type)); break;
                case 3: value.setCumulativeImportAmountValley(f64(input, type)); break;
                case 4: value.setCumulativeImportAmount(f64(input, type)); break;
                case 5: value.setDailyImportAmount((float) f64(input, type)); break;
                case 6: value.setMonthlyImportAmount(f64(input, type)); break;
                case 7: value.setDailyProfit(f64(input, type)); break;
                case 8: value.setYesterdayProfit(f64(input, type)); break;
                case 9: value.setMonthlyProfit(f64(input, type)); break;
                case 10: value.setLastMonthlyProfit(f64(input, type)); break;
                case 11: value.setCumulativeProfit(f64(input, type)); break;
                case 12: value.setCumulativeImportAmountSharp(f64(input, type)); break;
                case 15: value.setCumulativeImportKwh(f64(input, type)); break;
                case 16: value.setCumulativeImportKwhSharp(f64(input, type)); break;
                case 17: value.setCumulativeImportKwhPeak(f64(input, type)); break;
                case 18: value.setCumulativeImportKwhShoulder(f64(input, type)); break;
                case 19: value.setCumulativeImportKwhValley(f64(input, type)); break;
                case 20: value.setCumulativeExportKwh(f64(input, type)); break;
                case 21: value.setCumulativeExportKwhSharp(f64(input, type)); break;
                case 22: value.setCumulativeExportKwhPeak(f64(input, type)); break;
                case 23: value.setCumulativeExportKwhShoulder(f64(input, type)); break;
                case 24: value.setCumulativeExportKwhValley(f64(input, type)); break;
                case 25: value.setMonthlyImportKwh(f64(input, type)); break;
                case 26: value.setMonthlyImportKwhSharp(f64(input, type)); break;
                case 27: value.setMonthlyImportKwhPeak(f64(input, type)); break;
                case 28: value.setMonthlyImportKwhShoulder(f64(input, type)); break;
                case 29: value.setMonthlyImportKwhValley(f64(input, type)); break;
                case 30: value.setMonthlyExportKwh(f64(input, type)); break;
                case 31: value.setMonthlyExportKwhSharp(f64(input, type)); break;
                case 32: value.setMonthlyExportKwhPeak(f64(input, type)); break;
                case 33: value.setMonthlyExportKwhShoulder(f64(input, type)); break;
                case 34: value.setMonthlyExportKwhValley(f64(input, type)); break;
                case 35: value.setDailyImportKwh(f32(input, type)); break;
                case 36: value.setDailyImportKwhSharp(f32(input, type)); break;
                case 37: value.setDailyImportKwhPeak(f32(input, type)); break;
                case 38: value.setDailyImportKwhShoulder(f32(input, type)); break;
                case 39: value.setDailyImportKwhValley(f32(input, type)); break;
                case 40: value.setDailyExportKwh(f32(input, type)); break;
                case 41: value.setDailyExportKwhSharp(f32(input, type)); break;
                case 42: value.setDailyExportKwhPeak(f32(input, type)); break;
                case 43: value.setDailyExportKwhShoulder(f32(input, type)); break;
                case 44: value.setDailyExportKwhValley(f32(input, type)); break;
                case 45: value.setCumulativeExportAmount(f64(input, type)); break;
                case 46: value.setCumulativeExportAmountSharp(f64(input, type)); break;
                case 47: value.setCumulativeExportAmountPeak(f64(input, type)); break;
                case 48: value.setCumulativeExportAmountShoulder(f64(input, type)); break;
                case 49: value.setCumulativeExportAmountValley(f64(input, type)); break;
                case 50: value.setMonthlyImportAmountSharp(f64(input, type)); break;
                case 51: value.setMonthlyImportAmountPeak(f64(input, type)); break;
                case 52: value.setMonthlyImportAmountShoulder(f64(input, type)); break;
                case 53: value.setMonthlyImportAmountValley(f64(input, type)); break;
                case 54: value.setMonthlyExportAmount(f64(input, type)); break;
                case 55: value.setMonthlyExportAmountSharp(f64(input, type)); break;
                case 56: value.setMonthlyExportAmountPeak(f64(input, type)); break;
                case 57: value.setMonthlyExportAmountShoulder(f64(input, type)); break;
                case 58: value.setMonthlyExportAmountValley(f64(input, type)); break;
                case 59: value.setDailyImportAmountSharp(f32(input, type)); break;
                case 60: value.setDailyImportAmountPeak(f32(input, type)); break;
                case 61: value.setDailyImportAmountShoulder(f32(input, type)); break;
                case 62: value.setDailyImportAmountValley(f32(input, type)); break;
                case 63: value.setDailyExportAmount(f32(input, type)); break;
                case 64: value.setDailyExportAmountSharp(f32(input, type)); break;
                case 65: value.setDailyExportAmountPeak(f32(input, type)); break;
                case 66: value.setDailyExportAmountShoulder(f32(input, type)); break;
                case 67: value.setDailyExportAmountValley(f32(input, type)); break;
                case 68: value.setTime(u64(input, type)); break;
                default: skip(input, type);
            }
        });
        return value;
    }
    /** 解析电池堆实际字段。 */
    private UdanEmsDeviceState.BatteryStackState parseStack(byte[] b) throws IOException { UdanEmsDeviceState.BatteryStackState x = new UdanEmsDeviceState.BatteryStackState(); read(b, (f,t,in)-> { if(t==2&&f==2)x.setDeviceId(in.readString()); else if(t==5&&f==7)x.setTv(in.readFloat()); else if(t==5&&f==8)x.setTc(in.readFloat()); else if(t==5&&f==9)x.setSoh(in.readFloat()); else if(t==5&&f==10)x.setSoc(in.readFloat()); else if(t==5&&f==31)x.setRechargeableCapacityKwh(in.readFloat()); else if(t==5&&f==32)x.setDischargeableCapacityKwh(in.readFloat()); else if(t==1&&f==33)x.setCumulativeDischargeKwh(in.readDouble()); else if(t==1&&f==34)x.setCumulativeChargeKwh(in.readDouble()); else skip(in,t); }); return x; }
    /** 解析电池簇实际字段。 */
    private UdanEmsDeviceState.BatteryClusterState parseCluster(byte[] b) throws IOException { UdanEmsDeviceState.BatteryClusterState x = new UdanEmsDeviceState.BatteryClusterState(); read(b,(f,t,in)->{if(t==2&&f==2)x.setDeviceId(in.readString());else if(t==2&&f==3)x.setStackDeviceId(in.readString());else if(t==5&&f==7)x.setTv(in.readFloat());else if(t==5&&f==8)x.setTc(in.readFloat());else if(t==5&&f==9)x.setSoh(in.readFloat());else if(t==5&&f==10)x.setSoc(in.readFloat());else skip(in,t);});return x; }
    /** 解析电表实际字段。 */
    private UdanEmsDeviceState.ElectricMeterState parseMeter(byte[] b) throws IOException { UdanEmsDeviceState.ElectricMeterState x = new UdanEmsDeviceState.ElectricMeterState(); read(b,(f,t,in)->{if(t==2&&f==2)x.setDeviceId(in.readString());else if(t==1&&f==17)x.setCumulativeImportKwh(in.readDouble());else if(t==1&&f==22)x.setCumulativeExportKwh(in.readDouble());else if(t==5&&f==54)x.setAcTotalActivePower(in.readFloat());else skip(in,t);});return x; }
    /** 解析光伏实际字段。 */
    private UdanEmsDeviceState.PvState parsePv(byte[] b) throws IOException { UdanEmsDeviceState.PvState x = new UdanEmsDeviceState.PvState(); read(b,(f,t,in)->{if(t==2&&f==1)x.setDeviceId(in.readString());else if(t==5&&f==18)x.setActivePower(in.readFloat());else if(t==5&&f==19)x.setReactivePower(in.readFloat());else if(t==5&&f==20)x.setPowerFactor(in.readFloat());else if(t==5&&f==21)x.setDailyPowerGeneration(in.readFloat());else if(t==1&&f==22)x.setTotalPowerGeneration(in.readDouble());else skip(in,t);});return x; }
    /** 解析充电桩实际字段。 */
    private UdanEmsDeviceState.ChargeStationState parseChargeStation(byte[] b) throws IOException { UdanEmsDeviceState.ChargeStationState x = new UdanEmsDeviceState.ChargeStationState(); read(b,(f,t,in)->{if(t==2&&f==1)x.setDeviceId(in.readString());else if(t==0&&f==2)x.setTime(in.readUInt64());else skip(in,t);});return x; }
    private PcsState parsePcs(byte[] bytes) throws IOException {
        PcsState value = new PcsState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 2: value.setDeviceId(str(input, type)); break;
                case 3: value.setErrorStatus(enumName(input, type, "ErrorStatus")); break;
                case 4: value.setAlarmStatus(enumName(input, type, "AlarmStatus")); break;
                case 5: value.setRunStatus(enumName(input, type, "RunStatus")); break;
                case 6: value.setOnGridStatus(enumName(input, type, "GridStatus")); break;
                case 13: value.setAcTotalActivePower((double) f32(input, type)); break;
                case 17: value.setDailyChargeEnergyKwh((double) f32(input, type)); break;
                case 18: value.setDailyDischargeEnergyKwh((double) f32(input, type)); break;
                case 19: value.setTotalChargeEnergyKwh(f64(input, type)); break;
                case 20: value.setTotalDischargeEnergyKwh(f64(input, type)); break;
                case 31: value.setAPhaseActivePower((double) f32(input, type)); break;
                case 34: value.setBPhaseActivePower((double) f32(input, type)); break;
                case 37: value.setCPhaseActivePower((double) f32(input, type)); break;
                case 50: value.setControlMode(enumName(input, type, "PcsControlMode")); break;
                case 60: value.setCommStatus(enumName(input, type, "CommStatus")); break;
    /** 遍历嵌套 protobuf 字段,并把字段号、线类型和读取器交给解析器。 */
    private void read(byte[] b, FieldReader reader) throws IOException { CodedInputStream in=CodedInputStream.newInstance(b); while(!in.isAtEnd()){int tag=in.readTag();if(tag==0)break;reader.read(WireFormat.getTagFieldNumber(tag),WireFormat.getTagWireType(tag),in);} }
    /** 跳过当前不需要映射的字段。 */
    private void skip(CodedInputStream in, int type) throws IOException {
                default: skip(input, type);
            }
        });
        return value;
    }
    private BatteryStackState parseBatteryStack(byte[] bytes) throws IOException {
        BatteryStackState value = new BatteryStackState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 2: value.setDeviceId(str(input, type)); break;
                case 4: value.setWorkStatus(enumName(input, type, "BatteryWorkStatus")); break;
                case 5: value.setErrorStatus(enumName(input, type, "ErrorStatus")); break;
                case 6: value.setWarningStatus(enumName(input, type, "AlarmStatus")); break;
                case 7: value.setTv((double) f32(input, type)); break;
                case 8: value.setTc((double) f32(input, type)); break;
                case 9: value.setSoh((double) f32(input, type)); break;
                case 10: value.setSoc((double) f32(input, type)); break;
                case 15: value.setMinCellVoltMv((double) u32(input, type)); break;
                case 23: value.setMaxCellVoltMv((double) u32(input, type)); break;
                case 31: value.setRechargeableCapacityKwh((double) f32(input, type)); break;
                case 32: value.setDischargeableCapacityKwh((double) f32(input, type)); break;
                case 33: value.setCumulativeDischargeKwh(f64(input, type)); break;
                case 34: value.setCumulativeChargeKwh(f64(input, type)); break;
                case 36: value.setAvgCellTemp((double) u32(input, type) - 40D); break;
                case 52: value.setCommStatus(enumName(input, type, "CommStatus")); break;
                case 48: value.setBatteryStatus(enumName(input, type, "BatteryStatus")); break;
                default: skip(input, type);
            }
        });
        return value;
    }
    private ElectricMeterState parseElectricMeter(byte[] bytes) throws IOException {
        ElectricMeterState value = new ElectricMeterState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 2: value.setDeviceId(str(input, type)); break;
                case 48: value.setAPhaseVolt((double) f32(input, type)); break;
                case 49: value.setBPhaseVolt((double) f32(input, type)); break;
                case 50: value.setCPhaseVolt((double) f32(input, type)); break;
                case 51: value.setAPhaseCurrent((double) f32(input, type)); break;
                case 52: value.setBPhaseCurrent((double) f32(input, type)); break;
                case 53: value.setCPhaseCurrent((double) f32(input, type)); break;
                case 54: value.setAcTotalActivePower((double) f32(input, type)); break;
                case 69: value.setFrequency((double) f32(input, type)); break;
                case 74: value.setAcTotalPowerFactor((double) f32(input, type)); break;
                default: skip(input, type);
            }
        });
        return value;
    }
    private TmsState parseTms(byte[] bytes) throws IOException {
        TmsState value = new TmsState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 1: value.setDeviceId(str(input, type)); break;
                case 2: value.setRun_status(enumName(input, type, "TmsRunStatus")); break;
                case 3: value.setError_status(enumName(input, type, "ErrorStatus")); break;
                case 4: value.setSupply_temp((double) f32(input, type)); break;
                case 5: value.setReturn_temp((double) f32(input, type)); break;
                case 8: value.setEnv_temp((double) f32(input, type)); break;
                case 21: value.setCool_start_temp((double) f32(input, type)); break;
                case 22: value.setCool_stop_temp((double) f32(input, type)); break;
                case 23: value.setHeat_start_temp((double) f32(input, type)); break;
                case 24: value.setHeat_stop_temp((double) f32(input, type)); break;
                case 6: value.setTms_mode(enumName(input, type, "TmsMode")); break;
                case 28: value.setControl_mode(enumName(input, type, "TmsControlMode")); break;
                case 25: value.setComm_status(enumName(input, type, "CommStatus")); break;
                default: skip(input, type);
            }
        });
        return value;
    }
    private PeripheralDeviceState parsePeripheral(byte[] bytes) throws IOException {
        PeripheralDeviceState value = new PeripheralDeviceState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 1: value.setDeviceId(str(input, type)); break;
                case 2: value.setDiState(u32(input, type)); break;
                default: skip(input, type);
            }
        });
        value.convert();
        return value;
    }
    private void read(byte[] bytes, Reader reader) throws IOException {
        CodedInputStream input = CodedInputStream.newInstance(bytes == null ? new byte[0] : bytes);
        while (!input.isAtEnd()) {
            int tag = input.readTag();
            if (tag == 0) {
                break;
            }
            reader.read(WireFormat.getTagFieldNumber(tag), WireFormat.getTagWireType(tag), input);
        }
    }
    private static String str(CodedInputStream input, int type) throws IOException {
        if (type == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
            return input.readString();
        }
        skip(input, type);
        return null;
    }
    private static long u64(CodedInputStream input, int type) throws IOException {
        if (type == WireFormat.WIRETYPE_VARINT) {
            return input.readUInt64();
        }
        skip(input, type);
        return 0L;
    }
    private static int u32(CodedInputStream input, int type) throws IOException {
        return (int) u64(input, type);
    }
    private static float f32(CodedInputStream input, int type) throws IOException {
        if (type == WireFormat.WIRETYPE_FIXED32) {
            return input.readFloat();
        }
        skip(input, type);
        return 0F;
    }
    private static double f64(CodedInputStream input, int type) throws IOException {
        if (type == WireFormat.WIRETYPE_FIXED64) {
            return input.readDouble();
        }
        skip(input, type);
        return 0D;
    }
    private static String enumName(CodedInputStream input, int type, String prefix) throws IOException {
        if (type != WireFormat.WIRETYPE_VARINT) {
            skip(input, type);
            return null;
        }
        int number = input.readInt32();
        if ("RunStatus".equals(prefix)) {
            return new String[]{"RUN_STATUS_UNKNOWN", "RUN_STATUS_STOPPED", "RUN_STATUS_RUNNING"}[number < 0 || number > 2 ? 0 : number];
        }
        if ("GridStatus".equals(prefix)) {
            return new String[]{"GRID_STATUS_UNKNOWN", "GRID_STATUS_ON_GRID", "GRID_STATUS_OFF_GRID"}[number < 0 || number > 2 ? 0 : number];
        }
        if ("CommStatus".equals(prefix)) {
            return new String[]{"COMM_STATUS_UNKNOWN", "COMM_STATUS_DISCONNECTED", "COMM_STATUS_CONNECTED"}[number < 0 || number > 2 ? 0 : number];
        }
        if ("ErrorStatus".equals(prefix)) {
            return new String[]{"ERROR_STATUS_UNKNOWN", "ERROR_STATUS_OCCURRING", "ERROR_STATUS_NONE"}[number < 0 || number > 2 ? 0 : number];
        }
        if ("AlarmStatus".equals(prefix)) {
            return new String[]{"ALARM_STATUS_UNKNOWN", "ALARM_STATUS_OCCURRING", "ALARM_STATUS_NONE"}[number < 0 || number > 2 ? 0 : number];
        }
        if ("BatteryWorkStatus".equals(prefix)) {
            return new String[]{"BATTERY_WORK_STATUS_UNKNOWN", "BATTERY_WORK_STATUS_CHARGING", "BATTERY_WORK_STATUS_DISCHARGING", "BATTERY_WORK_STATUS_IDLE"}[number < 0 || number > 3 ? 0 : number];
        }
        if ("TmsRunStatus".equals(prefix)) {
            return new String[]{"TMS_RUN_STATUS_UNKNOWN", "TMS_RUN_STATUS_IDLE", "TMS_RUN_STATUS_READY_TO_START", "TMS_RUN_STATUS_RUNNING", "TMS_RUN_STATUS_DELAYED_SHUTDOWN", "TMS_RUN_STATUS_SELF_TEST", "TMS_RUN_STATUS_ALARMING"}[number < 0 || number > 6 ? 0 : number];
        }
        if ("PcsControlMode".equals(prefix)) {
            return new String[]{"PCS_CONTROL_MODE_UNKNOWN", "PCS_CONTROL_MODE_LOCAL_CONTROL", "PCS_CONTROL_MODE_REMOTE_CONTROL"}[number < 0 || number > 3 ? 0 : number];
        }
        if ("BatteryStatus".equals(prefix)) {
            return new String[]{"Battery_Status_BAN_CHARGE_DISCHARGE", "Battery_Status_ONLY_CHARGE", "Battery_Status_ONLY_DISCHARGE","Battery_Status_NORMAL","Battery_Status_BAN_CHARGE_DISCHARGE_NEW","Battery_Status_IDLE"}[number < 0 || number > 6 ? 0 : number];
        }
        if ("TmsMode".equals(prefix)) {
            return new String[]{"TMS_MODE_UNKNOWN", "TMS_MODE_REFRIGERATION", "TMS_MODE_HEATING","TMS_MODE_SELF_LOOP","TMS_MODE_AUTOMATIC","TMS_MODE_STOP"}[number < 0 || number > 6 ? 0 : number];
        }
        if ("TmsControlMode".equals(prefix)) {
            return new String[]{"UNKNOWN", "OUTLET_WATER_TEMP", "INLET_WATER_TEMP","CELL_WEIGHT_TEMP","CELL_AVG_TEMP"}[number < 0 || number > 5 ? 0 : number];
        }
        return prefix + "_" + number;
    }
    private static <T> T enumAt(T[] values, CodedInputStream input, int type) throws IOException {
        int number = (int) u64(input, type);
        return number >= 0 && number < values.length ? values[number] : null;
    }
    private static void skip(CodedInputStream input, int type) throws IOException {
        switch (type) {
            case WireFormat.WIRETYPE_VARINT: in.readInt64(); break;
            case WireFormat.WIRETYPE_FIXED64: in.readFixed64(); break;
            case WireFormat.WIRETYPE_LENGTH_DELIMITED: in.readBytes(); break;
            case WireFormat.WIRETYPE_FIXED32: in.readFixed32(); break;
            default: break;
            case WireFormat.WIRETYPE_VARINT:
                input.readInt64();
                break;
            case WireFormat.WIRETYPE_FIXED64:
                input.readFixed64();
                break;
            case WireFormat.WIRETYPE_LENGTH_DELIMITED:
                input.readBytes();
                break;
            case WireFormat.WIRETYPE_FIXED32:
                input.readFixed32();
                break;
            default:
                throw new IOException("Unsupported protobuf wire type: " + type);
        }
    }
    @FunctionalInterface private interface FieldReader { void read(int field, int type, CodedInputStream input) throws IOException; }
    @FunctionalInterface
    private interface Reader {
        void read(int field, int type, CodedInputStream input) throws IOException;
    }
}