|
|
@@ -1,9 +1,9 @@
|
|
|
/*
|
|
|
* 文 件 名: TransactionRecordFrame
|
|
|
* 版 权: 华设设计集团股份有限公司
|
|
|
- * 描 述: 交易记录帧
|
|
|
+ * 描 述: 交易记录帧 (0x3B)
|
|
|
* 修 改 人: lvwenbin
|
|
|
- * 修改时间: 2025/12/25
|
|
|
+ * 修改时间: 2026/01/08
|
|
|
*/
|
|
|
package com.ruoyi.ems.charging.model.req;
|
|
|
|
|
|
@@ -11,398 +11,490 @@ import com.ruoyi.ems.charging.model.BaseFrame;
|
|
|
import com.ruoyi.ems.charging.protocol.ProtocolConstants;
|
|
|
import com.ruoyi.ems.charging.utils.ByteUtils;
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
-import lombok.Data;
|
|
|
-import lombok.EqualsAndHashCode;
|
|
|
|
|
|
-import java.time.LocalDateTime;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.Date;
|
|
|
|
|
|
/**
|
|
|
* 交易记录帧 (0x3B)
|
|
|
- * 充电结束后上送的完整交易记录,包含分时电量统计
|
|
|
- *
|
|
|
- * 【重要】这是能源管理平台最核心的数据帧之一:
|
|
|
- * - 包含尖/峰/平/谷分时电量,用于能耗分析
|
|
|
- * - 包含电表起止值,用于电量核对
|
|
|
- * - 包含充电起止时间,用于负荷分析
|
|
|
+ * 充电结束后由充电桩上送的完整交易数据
|
|
|
+ *
|
|
|
+ * 消息体结构:
|
|
|
+ * - 交易流水号: 16字节 BCD码
|
|
|
+ * - 桩编号: 7字节 BCD码
|
|
|
+ * - 枪号: 1字节 BCD码
|
|
|
+ * - 开始时间: 7字节 CP56Time2a
|
|
|
+ * - 结束时间: 7字节 CP56Time2a
|
|
|
+ * - 分时电量区: 64字节 (尖/峰/平/谷各16字节)
|
|
|
+ * - 电表起止值: 10字节
|
|
|
+ * - 汇总区: 12字节
|
|
|
+ * - 附加信息区: 变长
|
|
|
*
|
|
|
* @author lvwenbin
|
|
|
- * @version [版本号, 2025/12/25]
|
|
|
+ * @version [版本号, 2026/01/08]
|
|
|
*/
|
|
|
-@Data
|
|
|
-@EqualsAndHashCode(callSuper = true)
|
|
|
public class TransactionRecordFrame extends BaseFrame {
|
|
|
|
|
|
+ // ==================== 基础信息 ====================
|
|
|
+
|
|
|
/**
|
|
|
- * 交易流水号 (BCD码 16字节)
|
|
|
+ * 交易流水号 (BCD 16字节)
|
|
|
*/
|
|
|
private String transactionNo;
|
|
|
|
|
|
/**
|
|
|
- * 桩编号 (BCD码 7字节)
|
|
|
+ * 桩编号 (BCD 7字节)
|
|
|
*/
|
|
|
private String pileCode;
|
|
|
|
|
|
/**
|
|
|
- * 枪号 (BCD码 1字节)
|
|
|
+ * 枪号 (BCD 1字节)
|
|
|
*/
|
|
|
private String gunNo;
|
|
|
|
|
|
/**
|
|
|
- * 开始时间 (CP56Time2a 7字节)
|
|
|
+ * 开始时间
|
|
|
*/
|
|
|
- private LocalDateTime startTime;
|
|
|
+ private Date startTime;
|
|
|
|
|
|
/**
|
|
|
- * 结束时间 (CP56Time2a 7字节)
|
|
|
+ * 结束时间
|
|
|
*/
|
|
|
- private LocalDateTime endTime;
|
|
|
+ private Date endTime;
|
|
|
|
|
|
- // ==================== 尖时段电量 ====================
|
|
|
-
|
|
|
- /**
|
|
|
- * 尖单价 (BIN 4字节) - 精确到五位小数 (电费+服务费)
|
|
|
- */
|
|
|
- private long sharpPrice;
|
|
|
+ // ==================== 分时电量区(每时段16字节) ====================
|
|
|
|
|
|
- /**
|
|
|
- * 尖电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
- */
|
|
|
- private long sharpEnergy;
|
|
|
+ // 尖时段
|
|
|
+ private int sharpUnitPrice; // 单价 (5位小数, 原始值)
|
|
|
+ private int sharpEnergy; // 电量 (4位小数, 原始值)
|
|
|
+ private int sharpEnergyLoss; // 计损电量 (4位小数, 原始值)
|
|
|
+ private int sharpAmount; // 金额 (4位小数, 原始值)
|
|
|
|
|
|
- /**
|
|
|
- * 计损尖电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
- */
|
|
|
- private long sharpLossEnergy;
|
|
|
+ // 峰时段
|
|
|
+ private int peakUnitPrice;
|
|
|
+ private int peakEnergy;
|
|
|
+ private int peakEnergyLoss;
|
|
|
+ private int peakAmount;
|
|
|
|
|
|
- /**
|
|
|
- * 尖金额 (BIN 4字节) - 精确到四位小数 (元)
|
|
|
- */
|
|
|
- private long sharpAmount;
|
|
|
+ // 平时段
|
|
|
+ private int flatUnitPrice;
|
|
|
+ private int flatEnergy;
|
|
|
+ private int flatEnergyLoss;
|
|
|
+ private int flatAmount;
|
|
|
+
|
|
|
+ // 谷时段
|
|
|
+ private int valleyUnitPrice;
|
|
|
+ private int valleyEnergy;
|
|
|
+ private int valleyEnergyLoss;
|
|
|
+ private int valleyAmount;
|
|
|
+
|
|
|
+ // ==================== 电表读数区 ====================
|
|
|
|
|
|
- // ==================== 峰时段电量 ====================
|
|
|
-
|
|
|
/**
|
|
|
- * 峰单价 (BIN 4字节) - 精确到五位小数
|
|
|
+ * 电表总起值 (BIN 5字节, 4位小数)
|
|
|
*/
|
|
|
- private long peakPrice;
|
|
|
+ private long meterStartValue;
|
|
|
|
|
|
/**
|
|
|
- * 峰电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 电表总止值 (BIN 5字节, 4位小数)
|
|
|
*/
|
|
|
- private long peakEnergy;
|
|
|
+ private long meterEndValue;
|
|
|
+
|
|
|
+ // ==================== 汇总区 ====================
|
|
|
|
|
|
/**
|
|
|
- * 计损峰电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 总电量 (BIN 4字节, 4位小数)
|
|
|
*/
|
|
|
- private long peakLossEnergy;
|
|
|
+ private int totalEnergy;
|
|
|
|
|
|
/**
|
|
|
- * 峰金额 (BIN 4字节) - 精确到四位小数 (元)
|
|
|
+ * 计损总电量 (BIN 4字节, 4位小数)
|
|
|
*/
|
|
|
- private long peakAmount;
|
|
|
+ private int totalEnergyLoss;
|
|
|
|
|
|
- // ==================== 平时段电量 ====================
|
|
|
-
|
|
|
/**
|
|
|
- * 平单价 (BIN 4字节) - 精确到五位小数
|
|
|
+ * 消费金额 (BIN 4字节, 4位小数)
|
|
|
*/
|
|
|
- private long flatPrice;
|
|
|
+ private int totalAmount;
|
|
|
+
|
|
|
+ // ==================== 附加信息区 ====================
|
|
|
|
|
|
/**
|
|
|
- * 平电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * VIN码 (ASCII 17字节)
|
|
|
*/
|
|
|
- private long flatEnergy;
|
|
|
+ private String vin;
|
|
|
|
|
|
/**
|
|
|
- * 计损平电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 交易标识 (1字节)
|
|
|
+ * 0x01: APP启动
|
|
|
+ * 0x02: 刷卡启动
|
|
|
+ * 0x04: 离线卡启动
|
|
|
+ * 0x05: VIN码启动
|
|
|
*/
|
|
|
- private long flatLossEnergy;
|
|
|
+ private byte transactionType;
|
|
|
|
|
|
/**
|
|
|
- * 平金额 (BIN 4字节) - 精确到四位小数 (元)
|
|
|
+ * 交易时间
|
|
|
*/
|
|
|
- private long flatAmount;
|
|
|
+ private Date transactionTime;
|
|
|
|
|
|
- // ==================== 谷时段电量 ====================
|
|
|
-
|
|
|
/**
|
|
|
- * 谷单价 (BIN 4字节) - 精确到五位小数
|
|
|
+ * 停止原因 (1字节)
|
|
|
*/
|
|
|
- private long valleyPrice;
|
|
|
+ private byte stopReason;
|
|
|
|
|
|
/**
|
|
|
- * 谷电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 物理卡号 (BIN 8字节)
|
|
|
*/
|
|
|
- private long valleyEnergy;
|
|
|
+ private String physicalCardNo;
|
|
|
|
|
|
/**
|
|
|
- * 计损谷电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 厂家自定义停止原因 (1字节, 可选)
|
|
|
*/
|
|
|
- private long valleyLossEnergy;
|
|
|
+ private byte vendorStopReason;
|
|
|
+
|
|
|
+ // ==================== 帧类型 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public byte getFrameType() {
|
|
|
+ return ProtocolConstants.FRAME_TYPE_TRANSACTION;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 编解码 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void encodeBody(ByteBuf buf) {
|
|
|
+ // 交易记录通常不需要平台发送,此方法留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void decodeBody(ByteBuf buf) {
|
|
|
+ // 交易流水号 (16字节 BCD)
|
|
|
+ byte[] transNoBytes = new byte[16];
|
|
|
+ buf.readBytes(transNoBytes);
|
|
|
+ this.transactionNo = ByteUtils.bcdToStr(transNoBytes);
|
|
|
+
|
|
|
+ // 桩编号 (7字节 BCD)
|
|
|
+ byte[] pileCodeBytes = new byte[7];
|
|
|
+ buf.readBytes(pileCodeBytes);
|
|
|
+ this.pileCode = ByteUtils.bcdToStr(pileCodeBytes);
|
|
|
+
|
|
|
+ // 枪号 (1字节 BCD)
|
|
|
+ this.gunNo = ByteUtils.bcdToStr(new byte[]{buf.readByte()});
|
|
|
+
|
|
|
+ // 开始时间 (7字节 CP56Time2a)
|
|
|
+ byte[] startTimeBytes = new byte[7];
|
|
|
+ buf.readBytes(startTimeBytes);
|
|
|
+ this.startTime = ByteUtils.cp56Time2aToDate(startTimeBytes);
|
|
|
+
|
|
|
+ // 结束时间 (7字节 CP56Time2a)
|
|
|
+ byte[] endTimeBytes = new byte[7];
|
|
|
+ buf.readBytes(endTimeBytes);
|
|
|
+ this.endTime = ByteUtils.cp56Time2aToDate(endTimeBytes);
|
|
|
+
|
|
|
+ // ========== 分时电量区 (64字节) ==========
|
|
|
+
|
|
|
+ // 尖时段 (16字节)
|
|
|
+ this.sharpUnitPrice = buf.readIntLE();
|
|
|
+ this.sharpEnergy = buf.readIntLE();
|
|
|
+ this.sharpEnergyLoss = buf.readIntLE();
|
|
|
+ this.sharpAmount = buf.readIntLE();
|
|
|
+
|
|
|
+ // 峰时段 (16字节)
|
|
|
+ this.peakUnitPrice = buf.readIntLE();
|
|
|
+ this.peakEnergy = buf.readIntLE();
|
|
|
+ this.peakEnergyLoss = buf.readIntLE();
|
|
|
+ this.peakAmount = buf.readIntLE();
|
|
|
+
|
|
|
+ // 平时段 (16字节)
|
|
|
+ this.flatUnitPrice = buf.readIntLE();
|
|
|
+ this.flatEnergy = buf.readIntLE();
|
|
|
+ this.flatEnergyLoss = buf.readIntLE();
|
|
|
+ this.flatAmount = buf.readIntLE();
|
|
|
+
|
|
|
+ // 谷时段 (16字节)
|
|
|
+ this.valleyUnitPrice = buf.readIntLE();
|
|
|
+ this.valleyEnergy = buf.readIntLE();
|
|
|
+ this.valleyEnergyLoss = buf.readIntLE();
|
|
|
+ this.valleyAmount = buf.readIntLE();
|
|
|
+
|
|
|
+ // ========== 电表读数区 (10字节) ==========
|
|
|
+
|
|
|
+ // 电表起值 (5字节, 小端序)
|
|
|
+ this.meterStartValue = ByteUtils.readLongLE(buf, 5);
|
|
|
+
|
|
|
+ // 电表止值 (5字节, 小端序)
|
|
|
+ this.meterEndValue = ByteUtils.readLongLE(buf, 5);
|
|
|
+
|
|
|
+ // ========== 汇总区 (12字节) ==========
|
|
|
+
|
|
|
+ this.totalEnergy = buf.readIntLE();
|
|
|
+ this.totalEnergyLoss = buf.readIntLE();
|
|
|
+ this.totalAmount = buf.readIntLE();
|
|
|
+
|
|
|
+ // ========== 附加信息区 ==========
|
|
|
+
|
|
|
+ // VIN码 (17字节 ASCII)
|
|
|
+ byte[] vinBytes = new byte[17];
|
|
|
+ buf.readBytes(vinBytes);
|
|
|
+ this.vin = new String(vinBytes, StandardCharsets.US_ASCII).trim();
|
|
|
+
|
|
|
+ // 交易标识 (1字节)
|
|
|
+ this.transactionType = buf.readByte();
|
|
|
+
|
|
|
+ // 交易时间 (7字节 CP56Time2a)
|
|
|
+ byte[] transTimeBytes = new byte[7];
|
|
|
+ buf.readBytes(transTimeBytes);
|
|
|
+ this.transactionTime = ByteUtils.cp56Time2aToDate(transTimeBytes);
|
|
|
+
|
|
|
+ // 停止原因 (1字节)
|
|
|
+ this.stopReason = buf.readByte();
|
|
|
+
|
|
|
+ // 物理卡号 (8字节 BIN)
|
|
|
+ byte[] cardNoBytes = new byte[8];
|
|
|
+ buf.readBytes(cardNoBytes);
|
|
|
+ this.physicalCardNo = ByteUtils.bytesToHex(cardNoBytes);
|
|
|
+
|
|
|
+ // 厂家自定义停止原因 (1字节, 可选)
|
|
|
+ if (buf.readableBytes() >= 1) {
|
|
|
+ this.vendorStopReason = buf.readByte();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 数值转换方法(原始值 -> 实际值) ====================
|
|
|
|
|
|
/**
|
|
|
- * 谷金额 (BIN 4字节) - 精确到四位小数 (元)
|
|
|
+ * 获取尖时段单价 (元/kWh, 5位小数)
|
|
|
*/
|
|
|
- private long valleyAmount;
|
|
|
+ public double getSharpUnitPriceValue() {
|
|
|
+ return sharpUnitPrice / 100000.0;
|
|
|
+ }
|
|
|
|
|
|
- // ==================== 电表读数 ====================
|
|
|
-
|
|
|
/**
|
|
|
- * 电表总起值 (BIN 5字节) - 精确到四位小数 (kWh)
|
|
|
+ * 获取尖时段电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- private long meterStartValue;
|
|
|
+ public double getSharpEnergyValue() {
|
|
|
+ return sharpEnergy / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 电表总止值 (BIN 5字节) - 精确到四位小数 (kWh)
|
|
|
+ * 获取尖时段计损电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- private long meterEndValue;
|
|
|
+ public double getSharpEnergyLossValue() {
|
|
|
+ return sharpEnergyLoss / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
- // ==================== 汇总数据 ====================
|
|
|
-
|
|
|
/**
|
|
|
- * 总电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 获取尖时段金额 (元, 4位小数)
|
|
|
*/
|
|
|
- private long totalEnergy;
|
|
|
+ public double getSharpAmountValue() {
|
|
|
+ return sharpAmount / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 计损总电量 (BIN 4字节) - 精确到四位小数 (kWh)
|
|
|
+ * 获取峰时段单价 (元/kWh, 5位小数)
|
|
|
*/
|
|
|
- private long totalLossEnergy;
|
|
|
+ public double getPeakUnitPriceValue() {
|
|
|
+ return peakUnitPrice / 100000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 消费金额 (BIN 4字节) - 精确到四位小数 (元)
|
|
|
- * 包含电费+服务费
|
|
|
+ * 获取峰时段电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- private long totalAmount;
|
|
|
+ public double getPeakEnergyValue() {
|
|
|
+ return peakEnergy / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 电动汽车唯一标识VIN (ASCII 17字节)
|
|
|
+ * 获取峰时段计损电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- private String vin;
|
|
|
+ public double getPeakEnergyLossValue() {
|
|
|
+ return peakEnergyLoss / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 交易标识 (BIN 1字节)
|
|
|
- * 0x01:app启动, 0x02:卡启动, 0x04:离线卡启动, 0x05:vin码启动
|
|
|
+ * 获取峰时段金额 (元, 4位小数)
|
|
|
*/
|
|
|
- private byte transactionType;
|
|
|
+ public double getPeakAmountValue() {
|
|
|
+ return peakAmount / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 交易日期时间 (CP56Time2a 7字节)
|
|
|
+ * 获取平时段单价 (元/kWh, 5位小数)
|
|
|
*/
|
|
|
- private LocalDateTime transactionTime;
|
|
|
+ public double getFlatUnitPriceValue() {
|
|
|
+ return flatUnitPrice / 100000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 停止原因 (BIN 1字节)
|
|
|
+ * 获取平时段电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- private int stopReason;
|
|
|
+ public double getFlatEnergyValue() {
|
|
|
+ return flatEnergy / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 物理卡号 (BIN 8字节)
|
|
|
+ * 获取平时段计损电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- private String physicalCardNo;
|
|
|
+ public double getFlatEnergyLossValue() {
|
|
|
+ return flatEnergyLoss / 10000.0;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * 桩厂家停止原因 (BIN 1字节) - 可选字段
|
|
|
+ * 获取平时段金额 (元, 4位小数)
|
|
|
*/
|
|
|
- private int manufacturerStopReason;
|
|
|
-
|
|
|
- @Override
|
|
|
- public byte getFrameType() {
|
|
|
- return ProtocolConstants.FRAME_TYPE_TRANSACTION;
|
|
|
+ public double getFlatAmountValue() {
|
|
|
+ return flatAmount / 10000.0;
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- protected void encodeBody(ByteBuf buf) {
|
|
|
- ByteUtils.writeBcd(buf, transactionNo, 16);
|
|
|
- ByteUtils.writeBcd(buf, pileCode, 7);
|
|
|
- ByteUtils.writeBcd(buf, gunNo, 1);
|
|
|
- ByteUtils.writeCp56Time2a(buf, startTime);
|
|
|
- ByteUtils.writeCp56Time2a(buf, endTime);
|
|
|
-
|
|
|
- // 尖时段
|
|
|
- buf.writeIntLE((int) sharpPrice);
|
|
|
- buf.writeIntLE((int) sharpEnergy);
|
|
|
- buf.writeIntLE((int) sharpLossEnergy);
|
|
|
- buf.writeIntLE((int) sharpAmount);
|
|
|
-
|
|
|
- // 峰时段
|
|
|
- buf.writeIntLE((int) peakPrice);
|
|
|
- buf.writeIntLE((int) peakEnergy);
|
|
|
- buf.writeIntLE((int) peakLossEnergy);
|
|
|
- buf.writeIntLE((int) peakAmount);
|
|
|
-
|
|
|
- // 平时段
|
|
|
- buf.writeIntLE((int) flatPrice);
|
|
|
- buf.writeIntLE((int) flatEnergy);
|
|
|
- buf.writeIntLE((int) flatLossEnergy);
|
|
|
- buf.writeIntLE((int) flatAmount);
|
|
|
-
|
|
|
- // 谷时段
|
|
|
- buf.writeIntLE((int) valleyPrice);
|
|
|
- buf.writeIntLE((int) valleyEnergy);
|
|
|
- buf.writeIntLE((int) valleyLossEnergy);
|
|
|
- buf.writeIntLE((int) valleyAmount);
|
|
|
-
|
|
|
- // 电表读数 (5字节)
|
|
|
- ByteUtils.writeUnsignedInt5LE(buf, meterStartValue);
|
|
|
- ByteUtils.writeUnsignedInt5LE(buf, meterEndValue);
|
|
|
-
|
|
|
- // 汇总
|
|
|
- buf.writeIntLE((int) totalEnergy);
|
|
|
- buf.writeIntLE((int) totalLossEnergy);
|
|
|
- buf.writeIntLE((int) totalAmount);
|
|
|
-
|
|
|
- ByteUtils.writeAscii(buf, vin, 17);
|
|
|
- buf.writeByte(transactionType);
|
|
|
- ByteUtils.writeCp56Time2a(buf, transactionTime);
|
|
|
- buf.writeByte(stopReason);
|
|
|
-
|
|
|
- // 物理卡号8字节
|
|
|
- byte[] cardBytes = ByteUtils.hexToBytes(physicalCardNo != null ? physicalCardNo : "");
|
|
|
- byte[] paddedCard = new byte[8];
|
|
|
- if (cardBytes.length > 0) {
|
|
|
- System.arraycopy(cardBytes, 0, paddedCard, 0, Math.min(cardBytes.length, 8));
|
|
|
- }
|
|
|
- buf.writeBytes(paddedCard);
|
|
|
+ /**
|
|
|
+ * 获取谷时段单价 (元/kWh, 5位小数)
|
|
|
+ */
|
|
|
+ public double getValleyUnitPriceValue() {
|
|
|
+ return valleyUnitPrice / 100000.0;
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- protected void decodeBody(ByteBuf buf) {
|
|
|
- this.transactionNo = ByteUtils.readBcd(buf, 16);
|
|
|
- this.pileCode = ByteUtils.readBcd(buf, 7);
|
|
|
- this.gunNo = ByteUtils.readBcd(buf, 1);
|
|
|
- this.startTime = ByteUtils.readCp56Time2a(buf);
|
|
|
- this.endTime = ByteUtils.readCp56Time2a(buf);
|
|
|
-
|
|
|
- // 尖时段
|
|
|
- this.sharpPrice = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.sharpEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.sharpLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.sharpAmount = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
-
|
|
|
- // 峰时段
|
|
|
- this.peakPrice = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.peakEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.peakLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.peakAmount = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
-
|
|
|
- // 平时段
|
|
|
- this.flatPrice = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.flatEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.flatLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.flatAmount = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
-
|
|
|
- // 谷时段
|
|
|
- this.valleyPrice = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.valleyEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.valleyLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.valleyAmount = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
-
|
|
|
- // 电表读数 (5字节)
|
|
|
- this.meterStartValue = ByteUtils.readUnsignedInt5LE(buf);
|
|
|
- this.meterEndValue = ByteUtils.readUnsignedInt5LE(buf);
|
|
|
-
|
|
|
- // 汇总
|
|
|
- this.totalEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.totalLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
- this.totalAmount = buf.readIntLE() & 0xFFFFFFFFL;
|
|
|
-
|
|
|
- this.vin = ByteUtils.readAscii(buf, 17);
|
|
|
- this.transactionType = buf.readByte();
|
|
|
- this.transactionTime = ByteUtils.readCp56Time2a(buf);
|
|
|
- this.stopReason = buf.readByte() & 0xFF;
|
|
|
-
|
|
|
- // 物理卡号8字节
|
|
|
- byte[] cardBytes = new byte[8];
|
|
|
- buf.readBytes(cardBytes);
|
|
|
- this.physicalCardNo = ByteUtils.bytesToHex(cardBytes).replace(" ", "");
|
|
|
-
|
|
|
- // 可选字段:桩厂家停止原因
|
|
|
- if (buf.readableBytes() >= 1) {
|
|
|
- this.manufacturerStopReason = buf.readByte() & 0xFF;
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 获取谷时段电量 (kWh, 4位小数)
|
|
|
+ */
|
|
|
+ public double getValleyEnergyValue() {
|
|
|
+ return valleyEnergy / 10000.0;
|
|
|
}
|
|
|
|
|
|
- // ==================== 能源管理核心方法 ====================
|
|
|
-
|
|
|
/**
|
|
|
- * 获取完整枪号
|
|
|
+ * 获取谷时段计损电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- public String getFullGunNo() {
|
|
|
- return pileCode + gunNo;
|
|
|
+ public double getValleyEnergyLossValue() {
|
|
|
+ return valleyEnergyLoss / 10000.0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取尖电量(kWh)
|
|
|
+ * 获取谷时段金额 (元, 4位小数)
|
|
|
*/
|
|
|
- public double getSharpEnergyValue() {
|
|
|
- return sharpEnergy / 10000.0;
|
|
|
+ public double getValleyAmountValue() {
|
|
|
+ return valleyAmount / 10000.0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取峰电量(kWh)
|
|
|
+ * 获取电表起值 (kWh, 4位小数)
|
|
|
*/
|
|
|
- public double getPeakEnergyValue() {
|
|
|
- return peakEnergy / 10000.0;
|
|
|
+ public double getMeterStartValueKwh() {
|
|
|
+ return meterStartValue / 10000.0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取平电量(kWh)
|
|
|
+ * 获取电表止值 (kWh, 4位小数)
|
|
|
*/
|
|
|
- public double getFlatEnergyValue() {
|
|
|
- return flatEnergy / 10000.0;
|
|
|
+ public double getMeterEndValueKwh() {
|
|
|
+ return meterEndValue / 10000.0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取谷电量(kWh)
|
|
|
+ * 获取电表差值 (kWh)
|
|
|
*/
|
|
|
- public double getValleyEnergyValue() {
|
|
|
- return valleyEnergy / 10000.0;
|
|
|
+ public double getMeterDeltaKwh() {
|
|
|
+ return getMeterEndValueKwh() - getMeterStartValueKwh();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取总电量(kWh)
|
|
|
+ * 获取总电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
public double getTotalEnergyValue() {
|
|
|
return totalEnergy / 10000.0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取计损总电量(kWh)
|
|
|
+ * 获取计损总电量 (kWh, 4位小数)
|
|
|
*/
|
|
|
- public double getTotalLossEnergyValue() {
|
|
|
- return totalLossEnergy / 10000.0;
|
|
|
+ public double getTotalEnergyLossValue() {
|
|
|
+ return totalEnergyLoss / 10000.0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取电表起值(kWh)
|
|
|
+ * 获取消费总金额 (元, 4位小数)
|
|
|
*/
|
|
|
- public double getMeterStartValueKwh() {
|
|
|
- return meterStartValue / 10000.0;
|
|
|
+ public double getTotalAmountValue() {
|
|
|
+ return totalAmount / 10000.0;
|
|
|
}
|
|
|
|
|
|
+ // ==================== 业务辅助方法 ====================
|
|
|
+
|
|
|
/**
|
|
|
- * 获取电表止值(kWh)
|
|
|
+ * 获取完整枪号(桩编号+枪号)
|
|
|
*/
|
|
|
- public double getMeterEndValueKwh() {
|
|
|
- return meterEndValue / 10000.0;
|
|
|
+ public String getFullGunNo() {
|
|
|
+ return pileCode + gunNo;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取电表差值(kWh) - 用于核对电量
|
|
|
+ * 获取充电时长(分钟)
|
|
|
*/
|
|
|
- public double getMeterDeltaKwh() {
|
|
|
- return getMeterEndValueKwh() - getMeterStartValueKwh();
|
|
|
+ public int getChargingDurationMinutes() {
|
|
|
+ if (startTime == null || endTime == null) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ long diffMs = endTime.getTime() - startTime.getTime();
|
|
|
+ return (int) (diffMs / 60000);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否正常停止
|
|
|
+ * 正常停止原因码: 0x40-0x49
|
|
|
+ */
|
|
|
+ public boolean isNormalStop() {
|
|
|
+ int code = stopReason & 0xFF;
|
|
|
+ return code >= 0x40 && code <= 0x49;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取充电时长(分钟)
|
|
|
+ * 获取停止原因描述
|
|
|
*/
|
|
|
- public long getChargingDurationMinutes() {
|
|
|
- if (startTime != null && endTime != null) {
|
|
|
- return java.time.Duration.between(startTime, endTime).toMinutes();
|
|
|
+ public String getStopReasonDesc() {
|
|
|
+ int code = stopReason & 0xFF;
|
|
|
+
|
|
|
+ // 正常结束 0x40-0x49
|
|
|
+ switch (code) {
|
|
|
+ case 0x40: return "达到SOC目标";
|
|
|
+ case 0x41: return "达到电压目标";
|
|
|
+ case 0x42: return "单体电压达到最高设定值";
|
|
|
+ case 0x43: return "达到金额目标";
|
|
|
+ case 0x44: return "达到时间目标";
|
|
|
+ case 0x45: return "达到电量目标";
|
|
|
+ case 0x46: return "BMS主动停止";
|
|
|
+ case 0x47: return "手动停止";
|
|
|
+ case 0x48: return "远程停止";
|
|
|
+ case 0x49: return "异常停止";
|
|
|
+
|
|
|
+ // 启动失败 0x4A-0x69
|
|
|
+ case 0x4A: return "绝缘故障";
|
|
|
+ case 0x4B: return "充电桩急停按下";
|
|
|
+ case 0x4C: return "门禁断开或柜门打开";
|
|
|
+ case 0x4D: return "电表通讯失败";
|
|
|
+ case 0x4E: return "熔断器断开";
|
|
|
+ case 0x4F: return "接收BMS充电准备报文超时";
|
|
|
+ case 0x50: return "接收BMS充电参数配置报文超时";
|
|
|
+ case 0x51: return "BRM报文超时";
|
|
|
+ case 0x52: return "接收BMS充电准备报文超时";
|
|
|
+ case 0x53: return "接收BCS报文超时";
|
|
|
+ case 0x54: return "BMS通讯超时";
|
|
|
+
|
|
|
+ // 异常终止 0x6A-0x8F
|
|
|
+ case 0x6A: return "充电模块过压保护";
|
|
|
+ case 0x6B: return "充电模块过流保护";
|
|
|
+ case 0x6C: return "充电模块短路保护";
|
|
|
+ case 0x6D: return "充电接口过温保护";
|
|
|
+ case 0x6E: return "充电模块过温保护";
|
|
|
+ case 0x6F: return "直流电压输出异常";
|
|
|
+ case 0x70: return "直流电流输出异常";
|
|
|
+ case 0x71: return "输入电压异常";
|
|
|
+ case 0x72: return "输入电流异常";
|
|
|
+ case 0x73: return "启动充电失败";
|
|
|
+ case 0x74: return "开关量输出异常";
|
|
|
+ case 0x75: return "BMS通讯异常";
|
|
|
+
|
|
|
+ case 0x90: return "其他原因";
|
|
|
+ default: return "未知原因(0x" + String.format("%02X", code) + ")";
|
|
|
}
|
|
|
- return 0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -414,113 +506,67 @@ public class TransactionRecordFrame extends BaseFrame {
|
|
|
case 0x02: return "刷卡启动";
|
|
|
case 0x04: return "离线卡启动";
|
|
|
case 0x05: return "VIN码启动";
|
|
|
- default: return "未知";
|
|
|
+ default: return "未知类型";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取停止原因描述
|
|
|
- */
|
|
|
- public String getStopReasonDesc() {
|
|
|
- return StopReasonCode.getDescription(stopReason);
|
|
|
+ // ==================== Getters ====================
|
|
|
+
|
|
|
+ public String getTransactionNo() {
|
|
|
+ return transactionNo;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 是否正常结束
|
|
|
- */
|
|
|
- public boolean isNormalStop() {
|
|
|
- // 0x40-0x49 为正常充电完成
|
|
|
- return stopReason >= 0x40 && stopReason <= 0x49;
|
|
|
+ public String getPileCode() {
|
|
|
+ return pileCode;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 是否启动失败
|
|
|
- */
|
|
|
- public boolean isStartFailed() {
|
|
|
- // 0x4A-0x69 为充电启动失败
|
|
|
- return stopReason >= 0x4A && stopReason <= 0x69;
|
|
|
+ public String getGunNo() {
|
|
|
+ return gunNo;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 是否异常中止
|
|
|
- */
|
|
|
- public boolean isAbnormalStop() {
|
|
|
- // 0x6A-0x8F 为充电异常中止
|
|
|
- return stopReason >= 0x6A && stopReason <= 0x8F;
|
|
|
+ public Date getStartTime() {
|
|
|
+ return startTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Date getEndTime() {
|
|
|
+ return endTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getVin() {
|
|
|
+ return vin;
|
|
|
+ }
|
|
|
+
|
|
|
+ public byte getTransactionType() {
|
|
|
+ return transactionType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Date getTransactionTime() {
|
|
|
+ return transactionTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ public byte getStopReason() {
|
|
|
+ return stopReason;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getPhysicalCardNo() {
|
|
|
+ return physicalCardNo;
|
|
|
+ }
|
|
|
+
|
|
|
+ public byte getVendorStopReason() {
|
|
|
+ return vendorStopReason;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
return String.format(
|
|
|
- "TransactionRecordFrame[transNo=%s, pile=%s, gun=%s, " +
|
|
|
- "energy=%.4fkWh(尖:%.4f,峰:%.4f,平:%.4f,谷:%.4f), " +
|
|
|
- "duration=%dmin, stopReason=%s]",
|
|
|
+ "TransactionRecord[transNo=%s, pile=%s, gun=%s, " +
|
|
|
+ "startTime=%s, endTime=%s, duration=%dmin, " +
|
|
|
+ "totalEnergy=%.4fkWh, totalAmount=%.2f元, " +
|
|
|
+ "vin=%s, stopReason=%s]",
|
|
|
transactionNo, pileCode, gunNo,
|
|
|
- getTotalEnergyValue(), getSharpEnergyValue(), getPeakEnergyValue(),
|
|
|
- getFlatEnergyValue(), getValleyEnergyValue(),
|
|
|
- getChargingDurationMinutes(), getStopReasonDesc());
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 停止原因代码枚举
|
|
|
- */
|
|
|
- public static class StopReasonCode {
|
|
|
-
|
|
|
- public static String getDescription(int code) {
|
|
|
- // 正常充电完成 0x40-0x49
|
|
|
- if (code == 0x40) return "APP远程停止";
|
|
|
- if (code == 0x41) return "SOC达到100%";
|
|
|
- if (code == 0x42) return "充电电量满足设定";
|
|
|
- if (code == 0x43) return "充电金额满足设定";
|
|
|
- if (code == 0x44) return "充电时间满足设定";
|
|
|
- if (code == 0x45) return "手动停止充电";
|
|
|
- if (code >= 0x46 && code <= 0x49) return "其他正常结束";
|
|
|
-
|
|
|
- // 充电启动失败 0x4A-0x69
|
|
|
- if (code == 0x4A) return "启动失败:控制系统故障";
|
|
|
- if (code == 0x4B) return "启动失败:控制导引断开";
|
|
|
- if (code == 0x4C) return "启动失败:断路器跳位";
|
|
|
- if (code == 0x4D) return "启动失败:电表通信中断";
|
|
|
- if (code == 0x4E) return "启动失败:余额不足";
|
|
|
- if (code == 0x4F) return "启动失败:充电模块故障";
|
|
|
- if (code == 0x50) return "启动失败:急停开入";
|
|
|
- if (code == 0x51) return "启动失败:防雷器异常";
|
|
|
- if (code == 0x52) return "启动失败:BMS未就绪";
|
|
|
- if (code == 0x53) return "启动失败:温度异常";
|
|
|
- if (code == 0x54) return "启动失败:电池反接";
|
|
|
- if (code == 0x55) return "启动失败:电子锁异常";
|
|
|
- if (code == 0x56) return "启动失败:合闸失败";
|
|
|
- if (code == 0x57) return "启动失败:绝缘异常";
|
|
|
- if (code >= 0x59 && code <= 0x63) return "启动失败:BMS通信超时";
|
|
|
- if (code == 0x64) return "启动失败:充电机未就绪";
|
|
|
- if (code >= 0x4A && code <= 0x69) return "启动失败:其他原因";
|
|
|
-
|
|
|
- // 充电异常中止 0x6A-0x8F
|
|
|
- if (code == 0x6A) return "异常中止:系统闭锁";
|
|
|
- if (code == 0x6B) return "异常中止:导引断开";
|
|
|
- if (code == 0x6C) return "异常中止:断路器跳位";
|
|
|
- if (code == 0x6D) return "异常中止:电表通信中断";
|
|
|
- if (code == 0x6E) return "异常中止:余额不足";
|
|
|
- if (code == 0x6F) return "异常中止:交流保护动作";
|
|
|
- if (code == 0x70) return "异常中止:直流保护动作";
|
|
|
- if (code == 0x71) return "异常中止:充电模块故障";
|
|
|
- if (code == 0x72) return "异常中止:急停开入";
|
|
|
- if (code == 0x73) return "异常中止:防雷器异常";
|
|
|
- if (code == 0x74) return "异常中止:温度异常";
|
|
|
- if (code == 0x75) return "异常中止:输出异常";
|
|
|
- if (code == 0x76) return "异常中止:充电无流";
|
|
|
- if (code == 0x77) return "异常中止:电子锁异常";
|
|
|
- if (code == 0x79) return "异常中止:总电压异常";
|
|
|
- if (code == 0x7A) return "异常中止:总电流异常";
|
|
|
- if (code == 0x7B) return "异常中止:单体电压异常";
|
|
|
- if (code == 0x7C) return "异常中止:电池组过温";
|
|
|
- if (code == 0x83) return "异常中止:充电桩断电";
|
|
|
- if (code >= 0x6A && code <= 0x8F) return "异常中止:其他原因";
|
|
|
-
|
|
|
- // 未知原因
|
|
|
- if (code == 0x90) return "未知原因停止";
|
|
|
-
|
|
|
- return "未知停止原因(0x" + Integer.toHexString(code) + ")";
|
|
|
- }
|
|
|
+ startTime, endTime, getChargingDurationMinutes(),
|
|
|
+ getTotalEnergyValue(), getTotalAmountValue(),
|
|
|
+ vin, getStopReasonDesc()
|
|
|
+ );
|
|
|
}
|
|
|
}
|