1103,1104,5,6指令,充电桩二维码下发和响应3,4指令

This commit is contained in:
红中
2025-09-03 17:44:46 +08:00
parent eff49bb3c0
commit 0582135995
16 changed files with 639 additions and 1 deletions

View File

@@ -0,0 +1,88 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
/**
* 充电桩状态枚举
*/
@AllArgsConstructor
@Getter
public enum LvnengGunCodeNameEnum {
GUN_QR_CODE_1(13, "1号枪二维码"),
GUN_QR_CODE_2(14, "2号枪二维码"),
GUN_QR_CODE_3(15, "3号枪二维码"),
GUN_QR_CODE_4(16, "4号枪二维码"),
GUN_QR_CODE_5(17, "5号枪二维码"),
GUN_QR_CODE_6(18, "6号枪二维码"),
GUN_QR_CODE_7(19, "7号枪二维码"),
GUN_QR_CODE_8(20, "8号枪二维码"),
GUN_QR_CODE_9(21, "9号枪二维码"),
GUN_QR_CODE_10(22, "10号枪二维码"),
GUN_QR_CODE_11(23, "11号枪二维码"),
GUN_QR_CODE_12(24, "12号枪二维码"),
GUN_QR_CODE_13(25, "13号枪二维码"),
GUN_QR_CODE_14(26, "14号枪二维码"),
GUN_QR_CODE_15(27, "15号枪二维码"),
GUN_QR_CODE_16(28, "16号枪二维码"),
GUN_QR_CODE_17(29, "17号枪二维码"),
GUN_QR_CODE_18(30, "18号枪二维码"),
GUN_QR_CODE_19(31, "19号枪二维码"),
GUN_QR_CODE_20(32, "20号枪二维码"),
GUN_QR_CODE_21(33, "21号枪二维码"),
GUN_QR_CODE_22(34, "22号枪二维码"),
GUN_QR_CODE_23(35, "23号枪二维码"),
GUN_QR_CODE_24(36, "24号枪二维码"),
GUN_QR_CODE_25(37, "25号枪二维码"),
GUN_QR_CODE_26(38, "26号枪二维码"),
GUN_QR_CODE_27(39, "27号枪二维码"),
GUN_QR_CODE_28(40, "28号枪二维码"),
GUN_QR_CODE_29(41, "29号枪二维码"),
GUN_QR_CODE_30(42, "30号枪二维码"),
GUN_QR_CODE_31(43, "31号枪二维码"),
GUN_QR_CODE_32(44, "32号枪二维码");
/**
* 参数地址
*/
private final int parameterAddress;
/**
* 枪二维码描述(便于理解枚举含义)
*/
private final String description;
private static final String UNKNOWN_DESC = "未知状态";
private static final Map<String, LvnengGunCodeNameEnum> CODE_TO_ENUM_MAP = new HashMap<>();
static {
for (LvnengGunCodeNameEnum enumValue : LvnengGunCodeNameEnum.values()) {
CODE_TO_ENUM_MAP.put(enumValue.getDescription(), enumValue);
}
}
public static Integer getParameterAddress(String description) {
LvnengGunCodeNameEnum lvnengPileStatusEnum = CODE_TO_ENUM_MAP.get(description);
if (lvnengPileStatusEnum == null) {
return null;
}
return lvnengPileStatusEnum.getParameterAddress();
}
}

View File

@@ -42,6 +42,9 @@ public class LvnengDownlinkCmdConverter implements DownlinkCmdConverter {
COMMAND_MAP.put(DownlinkCmdEnum.TRANSACTION_RECORD_ACK, 201);
COMMAND_MAP.put(DownlinkCmdEnum.HEARTBEAT_ACK, 101); // 心跳应答
COMMAND_MAP.put(DownlinkCmdEnum.REAL_TIME_DATA_ACK, 103); // 实时数据应答
COMMAND_MAP.put(DownlinkCmdEnum.SET_PRICING, 1103);//设置价格模型应答
COMMAND_MAP.put(DownlinkCmdEnum.REMOTE_STOP_CHARGING, 5);//远程停止充电
COMMAND_MAP.put(DownlinkCmdEnum.SET_QRCODE, 3);//设置二维码
// 绿能协议支持以上命令其他命令返回null
}

View File

@@ -0,0 +1,91 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.v340.cmd;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
import sanbing.jcpp.proto.gen.UplinkProto.RemoteStopChargingResponse;
import sanbing.jcpp.proto.gen.UplinkProto.UplinkQueueMessage;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.lvneng.LvnengUplinkCmdExe;
import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage;
import java.nio.charset.StandardCharsets;
import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340;
/**
* 绿能3.4 充电桩对服务器控制命令应答
*/
@Slf4j
@ProtocolCmd(value = 6, protocolNames = {V340})
public class LvnengV340RemoteStopAckULCmd extends LvnengUplinkCmdExe {
@Override
public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) {
log.debug("{} 绿能3.4 充电桩对服务器控制命令应答", tcpSession);
ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody());
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
// 从Tracer总获取当前时间
long ts = TracerContextUtil.getCurrentTracer().getTracerTs();
//1预留
byteBuf.skipBytes(2);
//2预留
byteBuf.skipBytes(2);
//3充电桩编码
byte[] pileCodeBytes = new byte[32];
byteBuf.readBytes(pileCodeBytes);
String pileCode = StringUtils.trim(new String(pileCodeBytes, StandardCharsets.US_ASCII));
//4 充电枪口
int gunCode = byteBuf.readByte();
additionalInfo.put("充电枪口", gunCode);
//5 命令启始标志
long flag = byteBuf.readUnsignedIntLE();
additionalInfo.put("命令启始标志", flag);
//6 命令个数
int paramCount = byteBuf.readByte();
additionalInfo.put("命令个数", paramCount);
// 7 命令执行结果 0x00成功 0x01失败
boolean isSuccess = (byteBuf.readByte() == 0x00);
tcpSession.addPileCode(pileCode);
RemoteStopChargingResponse remoteStopChargingResponse = RemoteStopChargingResponse.newBuilder()
.setPileCode(pileCode)
.setGunCode(gunCode+"")
.setSuccess(isSuccess)
.setAdditionalInfo(additionalInfo.toString())
.build();
// 转发到后端
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, lvnengUplinkMessage)
.setRemoteStopChargingResponse(remoteStopChargingResponse)
.build();
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
}
}

View File

@@ -0,0 +1,67 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.v340.cmd;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.proto.gen.DownlinkProto;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.lvneng.LvnengDownlinkCmdExe;
import sanbing.jcpp.protocol.lvneng.LvnengDwonlinkMessage;
import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.REMOTE_STOP_CHARGING;
import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340;
/**
* 绿能3.4 服务器下发充电桩控制命令
* 目前只支持停止充电
*/
@Slf4j
@ProtocolCmd(value = 5, protocolNames = {V340})
public class LvnengV340RemoteStopDLCmd extends LvnengDownlinkCmdExe {
@Override
public void execute(TcpSession tcpSession, LvnengDwonlinkMessage lvnengDwonlinkMessage, ProtocolContext ctx) {
log.debug("{} 绿能3.4 服务器下发充电桩控制命令", tcpSession);
if (!lvnengDwonlinkMessage.getMsg().hasRemoteStopChargingRequest()) {
return;
}
DownlinkProto.RemoteStopChargingRequest remoteStopChargingRequest = lvnengDwonlinkMessage.getMsg().getRemoteStopChargingRequest();
String pileCode = remoteStopChargingRequest.getPileCode();
String gunCode = remoteStopChargingRequest.getGunCode();
ByteBuf msgBody = Unpooled.buffer(44);
//1预留
msgBody.writeShortLE(0x00);
//2预留
msgBody.writeShortLE(0x00);
//3充电枪口
msgBody.writeByte(Integer.parseInt(gunCode));
//启始命令地址
msgBody.writeIntLE(2);
//命令个数
msgBody.writeByte(1);
//命令参数长度
msgBody.writeShortLE(4);
//命令参数
msgBody.writeIntLE(0x55);
encodeAndWriteFlush(REMOTE_STOP_CHARGING,
msgBody,
tcpSession);
}
}

View File

@@ -0,0 +1,49 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.v340.cmd;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.proto.gen.UplinkProto;
import sanbing.jcpp.proto.gen.UplinkProto.UplinkQueueMessage;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.lvneng.LvnengUplinkCmdExe;
import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage;
import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340;
/**
* 绿能3.4 充电桩应答服务器设置 24 时电费计价策略信息
*
*
*
*/
@Slf4j
@ProtocolCmd(value = 1104, protocolNames = {V340})
public class LvnengV340SetPricingModelAckULCmd extends LvnengUplinkCmdExe {
@Override
public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) {
log.debug("{} 绿能3.4 充电桩应答服务器设置 24 时电费计价策略信息", tcpSession);
ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody());
// 1.设置结果 0x00:成功 0x01:失败
boolean isSuccess = (byteBuf.readByte() == 0x00);
// 转发到后端
UplinkProto.SetPricingResponse setPricingResponse = UplinkProto.SetPricingResponse.newBuilder()
.setSuccess(isSuccess)
.build();
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(setPricingResponse.getPileCode(), tcpSession, lvnengUplinkMessage)
.setSetPricingResponse(setPricingResponse)
.build();
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
}
}

View File

@@ -0,0 +1,98 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.v340.cmd;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.proto.gen.DownlinkProto;
import sanbing.jcpp.proto.gen.DownlinkProto.SetPricingRequest;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.lvneng.LvnengDownlinkCmdExe;
import sanbing.jcpp.protocol.lvneng.LvnengDwonlinkMessage;
import sanbing.jcpp.protocol.lvneng.mapping.LvnengDownlinkCmdConverter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.SET_PRICING;
import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340;
/**
* 绿能3.4 服务器设置 24 时电费计价策略信息
*/
@Slf4j
@ProtocolCmd(value = 1103, protocolNames = {V340})
public class LvnengV340SetPricingModelDLCmd extends LvnengDownlinkCmdExe {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
@Override
public void execute(TcpSession tcpSession, LvnengDwonlinkMessage lvnengDwonlinkMessage, ProtocolContext ctx) {
log.debug("{} 绿能3.4服务器设置 24 时电费计价策略信息", tcpSession);
if (!lvnengDwonlinkMessage.getMsg().hasSetPricingRequest()) {
return;
}
SetPricingRequest setPricingRequest = lvnengDwonlinkMessage.getMsg().getSetPricingRequest();
long pricingId = setPricingRequest.getPricingId();
String pileCode = setPricingRequest.getPileCode();
DownlinkProto.PricingModelProto pricingModel = setPricingRequest.getPricingModel();
// 适配新的protobuf结构根据计费规则获取价格信息
List<DownlinkProto.TimePeriodItemProto> periodList = null;
if (pricingModel.hasTimePeriodPricing()) {
// 时段计价:使用运营商自定义时段计费
DownlinkProto.TimePeriodPricingProto timePeriodPricing = pricingModel.getTimePeriodPricing();
periodList = timePeriodPricing.getPeriodsList();
} else {
// 未知计费模式
log.error("未知的计费模式,桩编号: {}, 计费ID: {}, 计费规则: {}", pileCode, pricingId, pricingModel.getRule());
throw new IllegalArgumentException("未知的计费模式: " + pricingModel.getRule());
}
// 一个时间段开始小时1+开始分钟1+结束小时1+结束分钟1+4电费+4服务费*48个时间段
ByteBuf setPricingAckMsgBody = Unpooled.buffer(576);
for (DownlinkProto.TimePeriodItemProto x : periodList) {
LocalTime startTime = dateFormat(x.getStartTime());
setPricingAckMsgBody.writeByte(startTime.getHour());
setPricingAckMsgBody.writeByte(startTime.getMinute());
LocalTime endTime = dateFormat(x.getEndTime());
setPricingAckMsgBody.writeByte(endTime.getHour());
setPricingAckMsgBody.writeByte(endTime.getMinute());
setPricingAckMsgBody.writeIntLE(buildPrice(x.getElecPrice()));
setPricingAckMsgBody.writeIntLE(buildPrice(x.getServPrice()));
}
// 放进缓存后再下发
tcpSession.getRequestCache().put(pileCode + LvnengDownlinkCmdConverter.getInstance().convertToCmd(SET_PRICING), pricingId);
encodeAndWriteFlush(SET_PRICING,
setPricingAckMsgBody,
tcpSession);
}
protected static LocalTime dateFormat(String time) {
return LocalTime.parse(time, DATE_TIME_FORMATTER);
}
protected static int buildPrice(String price) {
return new BigDecimal(price).setScale(4, RoundingMode.HALF_UP).multiply(new BigDecimal(10000)).intValueExact();
}
}

View File

@@ -0,0 +1,70 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.v340.cmd;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import sanbing.jcpp.proto.gen.UplinkProto.UplinkQueueMessage;
import sanbing.jcpp.proto.gen.UplinkProto.SetQrcodeResponse;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.lvneng.LvnengUplinkCmdExe;
import sanbing.jcpp.protocol.lvneng.LvnengUplinkMessage;
import java.nio.charset.StandardCharsets;
import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340;
/**
* 绿能3.4 充电桩参数字符形设置应答
*/
@Slf4j
@ProtocolCmd(value = 4, protocolNames = {V340})
public class LvnengV340SetQrcodeModelAckULCmd extends LvnengUplinkCmdExe {
@Override
public void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx) {
log.debug("{} 绿能3.4 充电桩参数字符形设置/查询应答", tcpSession);
ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody());
//1预留
byteBuf.skipBytes(2);
//2预留
byteBuf.skipBytes(2);
//3充电桩编码
byte[] pileCodeBytes = new byte[32];
byteBuf.readBytes(pileCodeBytes);
String pileCode = StringUtils.trim(new String(pileCodeBytes, StandardCharsets.US_ASCII));
//4 0-査询 1-设置
int type = byteBuf.readByte();
//5启始地址
long startAddress = byteBuf.readUnsignedIntLE();
// 6.设置结果 0x00:成功 0x01:失败
boolean isSuccess = (byteBuf.readByte() == 0x00);
//todo 查询应答逻辑没有处理
// 转发到后端
SetQrcodeResponse setQrcodeResponse = SetQrcodeResponse.newBuilder()
.setPileCode(pileCode)
.setType(type)
.setStartAddr((int) startAddress)
.setSuccess(isSuccess)
.build();
UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(setQrcodeResponse.getPileCode(), tcpSession, lvnengUplinkMessage)
.setSetQrcodeResponse(setQrcodeResponse)
.build();
tcpSession.getForwarder().sendMessage(uplinkQueueMessage);
}
}

View File

@@ -0,0 +1,74 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.lvneng.v340.cmd;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.proto.gen.DownlinkProto.QrcodeModelProto;
import sanbing.jcpp.proto.gen.DownlinkProto.SetQrcodeRequest;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import sanbing.jcpp.protocol.listener.tcp.TcpSession;
import sanbing.jcpp.protocol.lvneng.LvnengDownlinkCmdExe;
import sanbing.jcpp.protocol.lvneng.LvnengDwonlinkMessage;
import sanbing.jcpp.protocol.lvneng.enums.LvnengGunCodeNameEnum;
import java.nio.charset.StandardCharsets;
import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.SET_QRCODE;
import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340;
/**
* 绿能3.4 服务器下发充电桩字符型参数
*/
@Slf4j
@ProtocolCmd(value = 3, protocolNames = {V340})
public class LvnengV340SetQrcodeModelDLCmd extends LvnengDownlinkCmdExe {
@Override
public void execute(TcpSession tcpSession, LvnengDwonlinkMessage lvnengDwonlinkMessage, ProtocolContext ctx) {
log.debug("{} 绿能3.4 服务器下发充电桩字符型参数", tcpSession);
if (!lvnengDwonlinkMessage.getMsg().hasSetQrcodeRequest()) {
return;
}
SetQrcodeRequest setQrcodeRequest = lvnengDwonlinkMessage.getMsg().getSetQrcodeRequest();
QrcodeModelProto qrcodeModel = setQrcodeRequest.getQrcodeModel();
Integer parameterAddress = LvnengGunCodeNameEnum.getParameterAddress(qrcodeModel.getGunName());
if (parameterAddress == null) {
log.error("{} 充电桩参数地址不存在", qrcodeModel.getGunName());
return;
}
ByteBuf msgBody = Unpooled.buffer(267);
// 预留1
msgBody.writeShortLE(0);
// 预留1
msgBody.writeShortLE(0);
//0-查询 1-设置
msgBody.writeByte(1);
// 4 参数起始地址,子命令
msgBody.writeIntLE(parameterAddress);
//6 参数字节长度
msgBody.writeShortLE(256);
//7 命令参数数据
msgBody.writeBytes(qrcodeModel.getCode().getBytes(StandardCharsets.US_ASCII));
//获取二维码参数地址
// 放进缓存后再下发
// tcpSession.getRequestCache().put(pileCode + LvnengDownlinkCmdConverter.getInstance().convertToCmd(SET_PRICING), pricingId);
encodeAndWriteFlush(SET_QRCODE,
msgBody,
tcpSession);
}
}