This commit is contained in:
2023-03-04 16:29:55 +08:00
commit 397ba75479
1007 changed files with 109050 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
package com.jsowell.netty.client;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.util.DateUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
public class NettyClient implements Runnable {
static final String HOST = System.getProperty("host", Constants.SOCKET_IP);
static final int PORT = Integer.parseInt(System.getProperty("port", "9011"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
private String content;
public NettyClient(String content) {
this.content = content;
}
@Override
public void run() {
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
int num = 0;
boolean boo = true;
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new NettyClientChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
p.addLast(new NettyClientHandler());
}
});
ChannelFuture future = b.connect(HOST, PORT).sync();
while (boo) {
num++;
future.channel().writeAndFlush("发送数据=======" + content + "--" + DateUtils.getTime());
try { //休眠一段时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每一条线程向服务端发送的次数
if (num == 5) {
boo = false;
}
}
log.info(content + "-----------------------------" + num);
//future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
/**
* 下面是不加线程的
*/
/*public static void main(String[] args) throws Exception {
sendMessage("hhh你好");
}
public static void sendMessage(String content) throws InterruptedException {
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new NettyClientChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
p.addLast(new NettyClientHandler());
}
});
ChannelFuture future = b.connect(HOST, PORT).sync();
future.channel().writeAndFlush(content);
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}*/
}

View File

@@ -0,0 +1,23 @@
package com.jsowell.netty.client;
import com.jsowell.netty.server.yunkuaichong.NettyServerHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* 客户端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器,客户端服务端编解码要一致
*/
public class NettyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
channel.pipeline().addLast(new NettyServerHandler());
}
}

View File

@@ -0,0 +1,71 @@
package com.jsowell.netty.client;
import com.jsowell.common.util.DateUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
/**
* 客户端处理类
*/
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 计算有多少客户端接入第一个string为客户端ip
*/
private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CLIENT_MAP = new ConcurrentHashMap<>();
@Override
public void channelActive(ChannelHandlerContext ctx) {
CLIENT_MAP.put(ctx.channel().id(), ctx);
log.info("ClientHandler Active");
}
/**
* @param ctx
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 有服务端端终止连接服务器会触发此函数
* @return: void
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
ctx.close();
log.info("服务端终止了服务");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("回写数据:" + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
//cause.printStackTrace();
log.info("服务端发生异常【" + cause.getMessage() + "");
ctx.close();
}
/**
* @param msg 需要发送的消息内容
* @param channelId 连接通道唯一id
* @author xiongchuan on 2019/4/28 16:10
* @DESCRIPTION: 客户端给服务端发送消息
* @return: void
*/
public void channelWrite(ChannelId channelId, String msg) {
ChannelHandlerContext ctx = CLIENT_MAP.get(channelId);
if (ctx == null) {
log.info("通道【" + channelId + "】不存在");
return;
}
//将客户端的信息直接返回写入ctx
ctx.write(msg + " 时间:" + DateUtils.getTime());
//刷新缓存区
ctx.flush();
}
}

View File

@@ -0,0 +1,14 @@
package com.jsowell.netty.client;
/**
* 模拟多客户端发送报文
*/
public class TestNettyClient {
public static void main(String[] args) {
//开启10条线程每条线程就相当于一个客户端
for (int i = 1; i <= 300; i++) {
new Thread(new NettyClient("thread" + "--" + i)).start();
}
}
}

View File

@@ -0,0 +1,18 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 读取实时监测数据命令
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetRealTimeMonitorDataCommand {
private String pileSn;
private String connectorCode;
}

View File

@@ -0,0 +1,14 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class IssueQRCodeCommand {
String pileSn;
}

View File

@@ -0,0 +1,17 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 对时命令
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProofreadTimeCommand {
private String pileSn;
}

View File

@@ -0,0 +1,17 @@
package com.jsowell.netty.command.ykc;
import com.jsowell.pile.vo.web.BillingTemplateVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PublishPileBillingTemplateCommand {
private String pileSn;
private BillingTemplateVO billingTemplateVO;
}

View File

@@ -0,0 +1,14 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RebootCommand {
private String pileSn;
}

View File

@@ -0,0 +1,48 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 启动充电指令
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class StartChargingCommand {
/**
* 交易流水号
*/
private String orderCode;
/**
* 充电桩编号
*/
private String pileSn;
/**
* 枪口号
*/
private String connectorCode;
/**
* 充电金额
*/
private BigDecimal chargeAmount;
/**
* 逻辑卡号 没有就传0
*/
private String logicCardNum;
/**
* 物理卡号 没有就传0
*/
private String physicsCardNum;
}

View File

@@ -0,0 +1,18 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 停止充电指令
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class StopChargingCommand {
private String pileSn;
private String connectorCode;
}

View File

@@ -0,0 +1,16 @@
package com.jsowell.netty.command.ykc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UpdateFileCommand {
List<String> pileSnList;
}

View File

@@ -0,0 +1,40 @@
package com.jsowell.netty.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import java.util.List;
public class CustomDecoder extends ByteToMessageDecoder {
private static final byte START_FLAG = (byte) 0x68;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 检查可读数据长度是否大于等于起始标志符和数据长度字段的长度
if (in.readableBytes() >= 3) {
// 标记当前读取位置
in.markReaderIndex();
// 读取起始标志符
byte startFlag = in.readByte();
// 检查起始标志符是否正确
if (startFlag != START_FLAG) {
// 如果不正确,重置读取位置,并抛出异常
in.resetReaderIndex();
throw new CorruptedFrameException("Invalid start flag: " + startFlag);
}
// 读取数据长度
byte length = in.readByte();
// 检查可读数据长度是否大于等于数据长度字段的值
if (in.readableBytes() >= length) {
// 读取完整数据包
ByteBuf frame = in.readBytes(length + 2); // 包括校验位
out.add(frame);
} else {
// 如果可读数据长度不够,重置读取位置,并等待下一次读取
in.resetReaderIndex();
}
}
}
}

View File

@@ -0,0 +1,85 @@
package com.jsowell.netty.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class StartAndLengthFieldFrameDecoder extends ByteToMessageDecoder {
// 起始标志
private int HEAD_DATA;
public StartAndLengthFieldFrameDecoder(int HEAD_DATA) {
this.HEAD_DATA = HEAD_DATA;
}
/**
* <pre>
* 协议开始的标准head_dataint类型占据1个字节.
* 表示数据的长度contentLengthint类型占据1个字节.
* </pre>
*/
public final int BASE_LENGTH = 1 + 1;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
List<Object> out) throws Exception {
System.out.println();
// log.info("打印ByteBuf:{}", buffer.toString());
// 可读长度必须大于基本长度
if (buffer.readableBytes() <= BASE_LENGTH) {
log.warn("可读字节数:{}小于基础长度:{}", buffer.readableBytes(), BASE_LENGTH);
return;
}
// 记录包头开始的index
int beginReader;
while (true) {
// 获取包头开始的index
beginReader = buffer.readerIndex();
// log.info("包头开始的index:{}", beginReader);
// 标记包头开始的index
buffer.markReaderIndex();
// 读到了协议的开始标志结束while循环
if (buffer.getUnsignedByte(beginReader) == HEAD_DATA) {
// log.info("读到了协议的开始标志结束while循环 byte:{}, HEAD_DATA:{}", buffer.getUnsignedByte(beginReader), HEAD_DATA);
break;
}
// 未读到包头,略过一个字节
// 每次略过,一个字节,去读取,包头信息的开始标记
buffer.resetReaderIndex();
buffer.readByte();
// 当略过,一个字节之后,
// 数据包的长度,又变得不满足
// 此时,应该结束。等待后面的数据到达
if (buffer.readableBytes() < BASE_LENGTH) {
log.info("数据包的长度不满足 readableBytes:{}, BASE_LENGTH:{}", buffer.readableBytes(), BASE_LENGTH);
return;
}
}
// 消息的长度
int length = buffer.getUnsignedByte(beginReader + 1);
// 判断请求数据包数据是否到齐
if (buffer.readableBytes() < length + 4) {
// log.info("请求数据包数据没有到齐,还原读指针 readableBytes:{}, 消息的长度:{}", buffer.readableBytes(), length);
// 还原读指针
buffer.readerIndex(beginReader);
return;
}
// 读取data数据
byte[] data = new byte[length + 4];
buffer.readBytes(data);
ByteBuf frame = buffer.retainedSlice(beginReader, length + 4);
buffer.readerIndex(beginReader + length + 4);
out.add(frame);
}
}

View File

@@ -0,0 +1,38 @@
package com.jsowell.netty.factory;
import com.google.common.collect.Maps;
import com.jsowell.common.util.StringUtils;
import com.jsowell.netty.handler.AbstractHandler;
import java.util.Map;
import java.util.Objects;
/**
* 工厂设计模式
* 云快充操作
*/
public class YKCOperateFactory {
private static Map<String, AbstractHandler> strategyMap = Maps.newHashMap();
/**
* 注册
* @param str
* @param handler
*/
public static void register(String str, AbstractHandler handler) {
if (StringUtils.isBlank(str) || Objects.isNull(handler)) {
return;
}
strategyMap.put(str, handler);
}
/**
* 获取
* @param name
* @return
*/
public static AbstractHandler getInvokeStrategy(String name) {
return strategyMap.get(name);
}
}

View File

@@ -0,0 +1,73 @@
package com.jsowell.netty.handler;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.CacheConstants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.CRC16Util;
import com.jsowell.common.util.DateUtils;
import io.netty.channel.Channel;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 模板方法模式
*/
public abstract class AbstractHandler implements InitializingBean {
@Autowired
private RedisCache redisCache;
/**
* 执行逻辑
* 有应答
*/
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
throw new UnsupportedOperationException();
}
/**
* 执行逻辑
* 不需要应答
*/
// public void pushProcess() {
// throw new UnsupportedOperationException();
// }
/**
* 组装应答的结果
* @param ykcDataProtocol 请求数据
* @param messageBody 消息体
* @return 应答结果
*/
protected byte[] getResult(YKCDataProtocol ykcDataProtocol, byte[] messageBody) {
// 起始标志
byte[] head = ykcDataProtocol.getHead();
// 序列号域
byte[] serialNumber = ykcDataProtocol.getSerialNumber();
// 加密标志
byte[] encryptFlag = ykcDataProtocol.getEncryptFlag();
// 请求帧类型
byte[] requestFrameType = ykcDataProtocol.getFrameType();
// 应答帧类型
byte[] responseFrameType = YKCFrameTypeCode.ResponseRelation.getResponseFrameType(requestFrameType);
// 数据域 值为“序列号域+加密标志+帧类型标志+消息体”字节数之和
byte[] dataFields = Bytes.concat(serialNumber, encryptFlag, responseFrameType, messageBody);
// 计算crc 从序列号域到数据域的 CRC 校验
int crc16 = CRC16Util.calcCrc16(dataFields);
return Bytes.concat(head, BytesUtil.intToBytes(dataFields.length, 1), dataFields, BytesUtil.intToBytes(crc16));
}
/**
* 保存桩最后链接到平台的时间
* @param pileSn 桩编号
*/
protected void saveLastTime(String pileSn) {
String redisKey = CacheConstants.PILE_LAST_CONNECTION + pileSn;
redisCache.setCacheObject(redisKey, DateUtils.getTime(), 60 * 60 * 24);
}
}

View File

@@ -0,0 +1,96 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电阶段 BMS 中止Handler
*
* GBT-27930 充电桩与 BMS 充电阶段 BMS 中止报文
* @author JS-ZZA
* @date 2022/9/19 13:32
*/
@Slf4j
@Component
public class BMSAbortDuringChargingPhaseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.BMS_ABORT_DURING_CHARGING_PHASE_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电阶段 BMS 中止===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(serialNumByteArr);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(pileConnectorNumByteArr);
/**
* BMS 中止充电原因
* 1-2 位——所需求的 SOC 目标值
* 3-4 位——达到总电压的设定值
* 5-6 位——达到单体电压设定值
* 7-8 位——充电机主动中止
*/
startIndex += length;
byte[] BMSStopReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* BMS 中止充电故障原因
* 1-2 位——绝缘故障
* 3-4 位——输出连接器过温故障
* 5-6 位——BMS 元件、输出连接 器过温
* 7-8 位——充电连接器故障
* 9-10 位——电池组温度过高故障
* 11-12 位——高压继电器故障
* 13-14 位——检测点 2 电压检测故障
* 15-16 位——其他故障
*/
startIndex += length;
length = 2;
byte[] BMSStopFaultReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* BMS 中止充电错误原因
* 1-2 位——电流过大
* 3-4 位——电压异常
* 5-8 位——预留位
*/
startIndex += length;
byte[] BMSStopErrorReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,108 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电过程 BMS 需求与充电机输出 0x23
*
* GBT-27930 充电桩与 BMS 充电过程 BMS 需求、充电机输出
* @author JS-ZZA
* @date 2022/9/19 13:51
*/
@Slf4j
@Component
public class BMSDemandAndChargerOutputHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGING_PROCESS_BMS_DEMAND_AND_CHARGER_OUTPUT_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电过程 BMS 需求与充电机输出===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电压需求 0.1 V/位, 0 V 偏移量
startIndex += length;
length = 2;
byte[] bmsVoltageDemandByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电流需求 0.1 A/位, -400 A 偏移量
startIndex += length;
byte[] bmsCurrentDemandByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 充电模式 0x01恒压充电 0x02恒流充电
startIndex += length;
length = 1;
byte[] bmsChargingModelByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 充电电压测量值 0.1 V/位, 0 V 偏移量
startIndex += length;
length = 2;
byte[] bmsChargingVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 充电电流测量值 0.1 A/位, -400 A 偏移量
startIndex += length;
byte[] bmsChargingCurrentByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 最高单体动力蓄电池电压及组号 1-12 位:最高单体动力蓄电池电压, 数据分辨率: 0.01 V/位, 0 V 偏移量;数据范围: 0~24 V 13-16 位: 最高单体动力蓄电池电 压所在组号,数据分辨率: 1/位, 0 偏移量;数据范围: 0~15
startIndex += length;
byte[] bmsMaxVoltageAndGroupNum = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 当前荷电状态 SOC % 1%/位, 0%偏移量; 数据范围: 0~100%
startIndex += length;
length = 1;
byte[] socByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 估算剩余充电时间 1 min/位, 0 min 偏移量; 数据范围: 0~600 min
startIndex += length;
length = 2;
byte[] bmsTheRestChargingTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩电压输出值 0.1 V/位, 0 V 偏移量
startIndex += length;
byte[] pileVoltageOutputByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩电流输出值 0.1 A/位, -400 A 偏移量
startIndex += length;
byte[] pileCurrentOutputByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 累计充电时间 1 min/位, 0 min 偏移量; 数据范围: 0~600 min
startIndex += length;
byte[] chargingTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,99 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电过程 BMS 信息 0x25
*
* GBT-27930 充电桩与 BMS 充电过程 BMS 信息
* @author JS-ZZA
* @date 2022/9/19 13:53
*/
@Slf4j
@Component
public class BMSInformationHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGING_PROCESS_BMS_INFORMATION_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电过程 BMS 信息===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 最高单体动力蓄电池电压所在编号 1/位, 1 偏移量; 数据范围: 1~256
startIndex += length;
byte[] BMSMaxVoltageNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 最高动力蓄电池温度 1ºC/位, -50 ºC 偏移量;数据范 围: -50 ºC ~+200 ºC
startIndex += length;
byte[] BMSMaxBatteryTemperatureByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 最高温度检测点编号 1/位, 1 偏移量; 数据范围: 1~128
startIndex += length;
byte[] maxTemperatureDetectionNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 最低动力蓄电池温度 1ºC/位, -50 ºC 偏移量;数据范 围: -50 ºC ~+200 ºC
startIndex += length;
byte[] minBatteryTemperature = BytesUtil.copyBytes(msgBody, startIndex, length);
// 最低动力蓄电池温度检测点编号 1/位, 1 偏移量; 数据范围: 1~128
startIndex += length;
byte[] minTemperatureDetectionNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* 9-12
* 9: BMS 单体动力蓄电池电压过高 /过低 (<00> =正常 ; <01> =过高 ; <10> =过低)
* 10: BMS 整车动力蓄电池荷电状态 SOC 过高/过低 (<00> =正常 ; <01> =过高 ; <10> =过低)
* 11: BMS 动力蓄电池充电过电流 (<00> =正常 ; <01> =过流 ; <10> =不可信状态)
* 12: BMS 动力蓄电池温度过高 (<00> =正常 ; <01> =过流 ; <10> =不可信状态)
*/
startIndex += length;
byte[] numNineToTwelve = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* 13-16
* 13: BMS 动力蓄电池绝缘状态 (<00> =正常 ; <01> =过流 ; <10> =不可信状态)
* 14: BMS 动力蓄电池组输出连接器连接状态 (<00> =正常 ; <01> =过流 ; <10> =不可信状态)
* 15: 充电禁止 (<00> =禁止; <01> =允许)
* 16: 预留位 00
*/
startIndex += length;
byte[] numThirteenToSixteen = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,78 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.command.ykc.IssueQRCodeCommand;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.netty.service.yunkuaichong.YKCPushCommandService;
import com.jsowell.pile.service.IPileBillingTemplateService;
import com.jsowell.pile.vo.web.BillingTemplateVO;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
/**
* 计费模板请求 Handler
*
* @author JS-ZZA
* @date 2022/9/17 15:59
*/
@Slf4j
@Component
public class BillingTemplateRequestHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.BILLING_TEMPLATE_CODE.getBytes());
@Autowired
private IPileBillingTemplateService pileBillingTemplateService;
@Autowired
private YKCPushCommandService ykcPushCommandService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行计费模板请求逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体(此请求消息体只有桩编码)
byte[] pileSnByte = ykcDataProtocol.getMsgBody();
String pileSn = BytesUtil.binary(pileSnByte, 16);
// log.info("桩号:{}", pileSn);
// 保存时间
saveLastTime(pileSn);
// 根据桩号查询计费模板
BillingTemplateVO billingTemplateVO = pileBillingTemplateService.selectBillingTemplateDetailByPileSn(pileSn);
if (billingTemplateVO == null) {
log.warn("根据桩号:{}查询计费模板为null", pileSn);
return null;
}
// log.info("下面进行下发二维码 pileSn:{}, thread:{}", pileSn, Thread.currentThread().getName());
// CompletableFuture.runAsync(() -> {
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// // 下发二维码
// IssueQRCodeCommand issueQRCodeCommand = IssueQRCodeCommand.builder().pileSn(pileSn).build();
// ykcPushCommandService.pushIssueQRCodeCommand(issueQRCodeCommand);
// });
byte[] messageBody = pileBillingTemplateService.generateBillingTemplateMsgBody(pileSn, billingTemplateVO);
return getResult(ykcDataProtocol, messageBody);
}
}

View File

@@ -0,0 +1,48 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 计费模型设置应答
*
* @author JS-ZZA
* @date 2022/9/27 11:31
*/
@Slf4j
@Component
public class BillingTemplateResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.BILLING_TEMPLATE_SETTING_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行计费模型设置应答逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, 0, 7);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 设置结果 0x00 失败 0x01 成功
byte[] settingResultByteArr = BytesUtil.copyBytes(msgBody, 7, 1);
return null;
}
}

View File

@@ -0,0 +1,61 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 计费模型设置
*
* 用户充电费用计算,每半小时为一个费率段,共 48 段,每段对应尖峰平谷其中一个费率 充电时桩屏幕按此费率分别显示已充电费和服务费
*
* @author JS-ZZA
* @date 2022/9/19 15:17
*/
@Slf4j
@Component
public class BillingTemplateSettingHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.BILLING_TEMPLATE_SETTING_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行计费模型设置逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 计费模型编码
// 尖电费费率 精确到五位小数
// 尖服务费费率
// 峰电费费率
// 峰服务费费率
// 平电费费率
// 平服务费费率
// 谷电费费率
// 谷服务费费率
// 计损比例
// 48个时段费率号 0尖费率 1峰费率 2平费率 3谷费率
return null;
}
}

View File

@@ -0,0 +1,89 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.netty.service.yunkuaichong.YKCPushCommandService;
import com.jsowell.pile.service.IPileBillingTemplateService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 计费模板验证请求Handler
*
* @author JS-ZZA
* @date 2022/9/17 14:10
*/
@Slf4j
@Component
public class BillingTemplateValidateRequestHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.BILLING_TEMPLATE_VALIDATE_CODE.getBytes());
@Autowired
private IPileBillingTemplateService pileBillingTemplateService;
@Autowired
private YKCPushCommandService ykcPushCommandService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行计费模板验证请求逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩号
byte[] pileSnByte = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.binary(pileSnByte, 16);
// 保存时间
saveLastTime(pileSn);
// 计费模型编码
startIndex += length;
length = 2;
byte[] billingTemplateCodeByte = BytesUtil.copyBytes(msgBody, startIndex, length);
String billingTemplateCode = BytesUtil.binary(billingTemplateCodeByte, 16);
// 根据桩号查询计费模板
// BillingTemplateVO billingTemplateVO = pileBillingTemplateService.selectBillingTemplateDetailByPileSn(pileSn);
// String templateCode = null;
// if (billingTemplateVO != null) {
// templateCode = billingTemplateVO.getTemplateCode();
// }
// log.info("桩传的计费模型编码:{}, 根据桩号:{} 查询到的计费模型编码:{}", billingTemplateCode, pileSn, templateCode);
// log.info("桩传的计费模型编码:{}", billingTemplateCode);
/**
* 应答 0x00 桩计费模型与平台一致 0x01 桩计费模型与平台不一致
*/
// byte[] flag;
// if (StringUtils.equals(billingTemplateCode, "100")){
// flag = Constants.zeroByteArray;
// }else {
// }
byte[] flag = Constants.oneByteArray;
// 消息体
byte[] messageBody = Bytes.concat(pileSnByte, billingTemplateCodeByte, flag);
return getResult(ykcDataProtocol, messageBody);
}
}

View File

@@ -0,0 +1,121 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.enums.ykc.OrderStatusEnum;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.IOrderBasicInfoService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Objects;
/**
* 充电结束Handler
*
* GBT-27930 充电桩与 BMS 充电结束阶段报文
* @author JS-ZZA
* @date 2022/9/19 13:27
*/
@Slf4j
@Component
public class ChargeEndHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGE_END_CODE.getBytes());
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行充电结束逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(serialNumByteArr);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr);
// BMS中止荷电状态 SOC 1%/位, 0%偏移量; 数据范围: 0~100%
startIndex += length;
byte[] BMSStopChargingSOC = BytesUtil.copyBytes(msgBody, startIndex, length);
String stopSoc = BytesUtil.binary(BMSStopChargingSOC, 10);
// BMS 动力蓄电池单体最低电压 0.01 V/位, 0 V 偏移量;数据范围: 0 ~24 V
startIndex += length;
length = 2;
byte[] BMSBatteryMinVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 动力蓄电池单体最高电压 0.01 V/位, 0 V 偏移量;数据范围: 0 ~24 V
startIndex += length;
byte[] BMSBatteryMaxVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 动力蓄电池最低温度 1ºC/位, -50 ºC 偏移量;数据范围: -50 ºC ~+200 ºC
startIndex += length;
length = 1;
byte[] BMSBatteryMinTemperatureByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 动力蓄电池最高温度 1ºC/位, -50 ºC 偏移量;数据范围: -50 ºC ~+200 ºC
startIndex += length;
byte[] BMSBatteryMaxTemperatureByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩累计充电时间 1 min/位, 0 min 偏移量; 数据范围: 0~600 min
startIndex += length;
length = 2;
byte[] pileSumChargingTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩输出能量 0.1 kWh/位, 0 kWh 偏移量; 数据范围: 0~1000 kWh
startIndex += length;
byte[] pileOutputEnergyByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩充电机编号 充电机编号, 1/位, 1偏移量 ,数据范围 0 0xFFFFFFFF
startIndex += length;
length = 4;
byte[] pileChargedCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 查询订单,改为待结算 将结束soc传入
OrderBasicInfo orderInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode);
if (Objects.nonNull(orderInfo)) {
if (StringUtils.equals(OrderStatusEnum.IN_THE_CHARGING.getValue(), orderInfo.getOrderStatus())) {
orderInfo.setOrderStatus(OrderStatusEnum.STAY_SETTLEMENT.getValue());
}
orderInfo.setEndSOC(stopSoc);
if (orderInfo.getChargeEndTime() == null) {
orderInfo.setChargeEndTime(new Date()); // 结束充电时间
}
orderBasicInfoService.updateOrderBasicInfo(orderInfo);
}
return null;
}
}

View File

@@ -0,0 +1,93 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电阶段充电机中止Handler 0x21
*
* GBT-27930 充电桩与 BMS 充电阶段充电机中止报文
* @author JS-ZZA
* @date 2022/9/19 13:35
*/
@Slf4j
@Component
public class ChargerAbortedDuringChargingPhaseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.THE_CHARGER_IS_ABORTED_DURING_THE_CHARGING_PHASE_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电阶段充电机中止===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* BMS 中止充电原因
* 1-2 位——达到充电机设定 的条件中止
* 3-4 位——人工中止
* 5-6 位——异常中止
* 7-8 位——BMS 主动中止
*/
startIndex += length;
byte[] BMSStopChargingReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* BMS 中止充电故障原因
* 1-2 位——充电机过温故障
* 3-4 位——充电连接器故障
* 5-6 位——充电机内部过温故障
* 7-8 位——所需电量不 能传送
* 9-10 位——充电机急停故障
* 11-12 位——其他故障
* 13-16 位——预留位
*/
startIndex += length;
length = 2;
byte[] BMSStopChargingFaultReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
/**
* BMS 中止充电错误原因
* 1-2 位——电流不匹配
* 3-4 位——电压异常
* 5-8 位——预留位
*/
startIndex += length;
length = 1;
byte[] BMSStopChargingErrorReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,125 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电握手
*
* GBT-27930 充电桩与 BMS 充电握手阶段报文
* @author JS-ZZA
* @date 2022/9/19 13:20
*/
@Slf4j
@Component
public class ChargingHandshakeHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGING_HANDSHAKE_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行充电握手逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 通信协议版本号 当前版本为 V1.1 表示为: byte3 byte2—0001Hbyte1—01H
startIndex += length;
length = 3;
byte[] BMSCommunicationProtocolVersionByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池类型 01H:铅酸电池;02H:氢 电池;03H:磷酸铁锂电池;04H:锰 酸锂电池;05H:钴酸锂电池 ;06H: 三元材料电池;07H:聚合物锂离子 电池;08H:钛酸锂电池;FFH:其他
startIndex += length;
length = 1;
byte[] BMSBatteryTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 整车动力蓄电池系统额定容量 0.1 Ah /位, 0 Ah 偏移量
startIndex += length;
length = 2;
byte[] BMSBatteryCapacityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 整车动力蓄电池系统额定总电压 0.1V/位, 0V 偏移量
startIndex += length;
byte[] BMSBatteryVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池生产厂商名称
startIndex += length;
length = 4;
byte[] BMSBatteryFactoryByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池组序号
startIndex += length;
byte[] BMSBatteryNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池组生产日期年 1985 年偏移量, 数据范围: 1985 2235 年
startIndex += length;
length = 1;
byte[] BMSProductionDateYearByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池组生产日期月 0 月偏移量, 数据范围: 112 月
startIndex += length;
byte[] BMSProductionDateMonthByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池组生产日期日 0 日偏移量, 数据范围: 131 日
startIndex += length;
byte[] BMSProductionDateDayByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池组充电次数 1 次/位, 0 次偏移量, 以 BMS 统 计为准
startIndex += length;
length = 3;
byte[] BMSChargingTimesByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 电池组产权标识 <0> =租赁; <1> =车自有)
startIndex += length;
length = 1;
byte[] BMSPropertyIdentificationByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 预留位
startIndex += length;
byte[] BMSReservePosition = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 车辆识别码
startIndex += length;
length = 17;
byte[] BMSCarIdentifyCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 软件版本号
startIndex += length;
length = 8;
byte[] BMSSoftwareVersionByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,135 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.common.util.id.IdUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.service.impl.MemberBasicInfoServiceImpl;
import com.jsowell.pile.vo.uniapp.MemberVO;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
/**
* 充电桩主动申请启动充电 0x31
*
* 启动充电鉴权结果
* @author JS-ZZA
* @date 2022/9/19 14:29
*/
@Slf4j
@Component
public class ConfirmStartChargingRequestHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REQUEST_START_CHARGING_CODE.getBytes());
@Autowired
private MemberBasicInfoServiceImpl memberBasicInfoService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电桩主动申请启动充电===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(pileConnectorNumByteArr);
// 启动方式
// 0x01 表示通过刷卡启动充电
// 0x02 表求通过帐号启动充电 (暂不支持)
// 0x03 表示vin码启动充电
startIndex += length;
length = 1;
byte[] startModeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 是否需要密码 0x00 不需要 0x01 需要
startIndex += length;
byte[] needPasswordFlagByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 物理卡号 不足 8 位补 0
startIndex += length;
length = 8;
byte[] cardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String physicsCardNum = BytesUtil.bcd2Str(cardNumByteArr);
// 输入密码 对用户输入的密码进行16 位MD5 加密,采用小写上传
startIndex += length;
length = 16;
byte[] inputPasswordByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// VIN码
startIndex += length;
length = 17;
byte[] vinCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 应答
// 交易流水号
String serialNum = IdUtils.generateOrderCode(pileSn, connectorCode);
byte[] serialNumByteArr = BytesUtil.str2Bcd(serialNum);
// 逻辑卡号
String logicCardNum = "00000000";
byte[] logicCardNumByteArr = BytesUtil.str2Bcd(logicCardNum); // [0, 0, 0, 0]
// 账户余额 保留两位小数
MemberVO memberVO = memberBasicInfoService.selectInfoByPhysicsCard(physicsCardNum);
BigDecimal principalBalance = memberVO.getPrincipalBalance(); // 本金金额
double accountBalance = principalBalance.add(memberVO.getGiftBalance()).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
byte[] accountBalanceByteArr = BytesUtil.str2Bcd(String.valueOf(accountBalance));
// 鉴权成功标识 0x00 失败 0x01 成功
byte[] authenticationFlagByteArr= Constants.oneByteArray;
/**
* 失败原因
* 0x01 账户不存在
* 0x02 账户冻结
* 0x03 账户余额不足
* 0x04 该卡存在未结账记录
* 0x05 桩停用
* 0x06 该账户不能在此桩上充电
* 0x07 密码错误
* 0x08 电站电容不足
* 0x09 系统中vin 码不存在
* 0x0A 该桩存在未结账记录
* 0x0B 该桩不支持刷卡
*/
byte[] defeatReasonByteArr = Constants.zeroByteArray;
// 拼装消息体
byte[] msgBodyByteArr = Bytes.concat(serialNumByteArr, logicCardNumByteArr, accountBalanceByteArr,
authenticationFlagByteArr, defeatReasonByteArr);
return getResult(ykcDataProtocol, msgBodyByteArr);
}
}

View File

@@ -0,0 +1,93 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 错误报文Handler
*
* GBT-27930 充电桩与 BMS 充电错误报文
* @author JS-ZZA
* @date 2022/9/19 13:29
*/
@Slf4j
@Component
public class ErrorMessageHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.ERROR_MESSAGE_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===错误报文===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号4-6 <00> =正常; <01> =超时; <10> =不可信状态
startIndex += length;
byte[] NumFourToSix = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号7-9
startIndex += length;
byte[] NumSevenToNine = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号10-12
startIndex += length;
byte[] NumTenToTwelve = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号13-14
startIndex += length;
byte[] NumThirteenToFourteen = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号15-16
startIndex += length;
byte[] NumFifteenToSixteen = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号17-19
startIndex += length;
byte[] NumSeventeenToNineteen = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号20-23
startIndex += length;
byte[] NumTwentyToTwentyThree = BytesUtil.copyBytes(msgBody, startIndex, length);
// 序号24-25
startIndex += length;
byte[] NumTwentyFourToTwentyFive = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,58 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 地锁数据上送
*
* 地锁状态/报警信息变化时,桩立刻上送变位/报警信息;地锁状态不变化时,每隔 5 分钟周期 性上送地锁状态。若无报警信息,不上送。
*
* @author JS-ZZA
* @date 2022/9/19 15:21
*/
@Slf4j
@Component
public class GroundLockDataUploadHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.GROUND_LOCK_DATA_UPLOAD_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===地锁数据上送===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
// 桩编码
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, 0, 7);
// 枪号
byte[] connectorByteArr = BytesUtil.copyBytes(msgBody, 7, 1);
/**
* 车位锁状态
* 0x00未到位状态
* 0x55升锁到位状态
* 0xFF降锁到位状态
*/
byte[] parkingLockStatusByteArr = BytesUtil.copyBytes(msgBody, 8, 1);
// 车位状态 0x00无车辆 0xFF停放车辆
byte[] parkingStatusByteArr = BytesUtil.copyBytes(msgBody, 9, 1);
// 地锁电量状态 百分比值0~100
byte[] groundLockElectricByteArr = BytesUtil.copyBytes(msgBody, 10, 1);
// 报警状态 0x00正常无报警 0xFF待机状态摇臂破坏 0x55摇臂升降异常(未到位)
byte[] alarmStatusByteArr = BytesUtil.copyBytes(msgBody, 11, 1);
// 预留位 全部置0
byte[] waitingUseByteArr = BytesUtil.copyBytes(msgBody, 12, 4);
return null;
}
}

View File

@@ -0,0 +1,83 @@
package com.jsowell.netty.handler;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.service.IPileBasicInfoService;
import com.jsowell.pile.service.IPileConnectorInfoService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 充电桩心跳包
*/
@Slf4j
@Component
public class HeartbeatRequestHandler extends AbstractHandler {
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.HEART_BEAT_CODE.getBytes());
@Autowired
private RedisCache redisCache;
@Autowired
private IPileBasicInfoService pileBasicInfoService;
@Autowired
private IPileConnectorInfoService pileConnectorInfoService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
// log.info("[===充电桩心跳包===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩号
byte[] pileSnByte = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.binary(pileSnByte, 16);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByte = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileConnectorNum = String.format("%02d", Integer.parseInt(BytesUtil.binary(pileConnectorNumByte, 16)));
//枪状态(不回复)
startIndex += length;
length = 1;
byte[] connectorStatusByte = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorStatus = BytesUtil.binary(connectorStatusByte, 16);
// log.info("桩号:{}, 枪号:{}, 枪状态:{}", pileSn, pileConnectorNum, connectorStatus);
// updateStatus(pileSn, pileConnectorNum, connectorStatus);
// 公共方法修改状态
pileBasicInfoService.updateStatus(BytesUtil.bcd2Str(ykcDataProtocol.getFrameType()), pileSn, pileConnectorNum, connectorStatus, null);
// 心跳应答置0
byte[] flag = Constants.zeroByteArray;
// 消息体
byte[] messageBody = Bytes.concat(pileSnByte, pileConnectorNumByte, flag);
return getResult(ykcDataProtocol, messageBody);
}
}

View File

@@ -0,0 +1,189 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.LoginRequestData;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.enums.ykc.PileChannelEntity;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.command.ykc.IssueQRCodeCommand;
import com.jsowell.netty.command.ykc.ProofreadTimeCommand;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.netty.service.yunkuaichong.YKCPushCommandService;
import com.jsowell.pile.service.IPileBasicInfoService;
import com.jsowell.pile.service.IPileMsgRecordService;
import com.jsowell.pile.vo.base.PileInfoVO;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Component
public class LoginRequestHandler extends AbstractHandler{
@Autowired
private IPileBasicInfoService pileBasicInfoService;
@Autowired
private YKCPushCommandService ykcPushCommandService;
@Autowired
private IPileMsgRecordService pileMsgRecordService;
@Autowired
private RedisCache redisCache;
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.LOGIN_CODE.getBytes());
private List<String> newProgramVersionList = Lists.newArrayList("c6-30");
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===执行登录逻辑===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编码
byte[] pileSnByte = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.binary(pileSnByte, 16);
// log.info("桩号:{}", pileSn);
// 保存时间
saveLastTime(pileSn);
// 桩类型 0 表示直流桩, 1 表示交流桩
startIndex += length;
length = 1;
byte[] pileTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileType = BytesUtil.bcd2Str(pileTypeByteArr);
// 充电枪数量
startIndex += length;
byte[] connectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorNum = BytesUtil.bcd2Str(connectorNumByteArr);
// 通信协议版本 版本号乘 10v1.0 表示 0x0A
startIndex += length;
byte[] communicationVersionByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// int i = Integer.parseInt(BytesUtil.bcd2Str(communicationVersionByteArr)); // 0F --> 15
BigDecimal bigDecimal = new BigDecimal(BytesUtil.bcd2Str(communicationVersionByteArr));
BigDecimal communicationVersionTemp = bigDecimal.divide(new BigDecimal(10));
String communicationVersion = "v" + communicationVersionTemp;
// 程序版本
startIndex += length;
length = 8;
byte[] programVersionByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String programVersion = BytesUtil.ascii2Str(programVersionByteArr);
// log.info("程序版本:{} length:{}", programVersion, programVersion.length());
// 网络连接类型 0x00 SIM 卡 0x01 LAN 0x02 WAN 0x03 其他
startIndex += length;
length = 1;
byte[] internetConnectionTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String internetConnection = BytesUtil.bcd2Str(internetConnectionTypeByteArr);
// sim卡
startIndex += length;
length = 10;
byte[] simCardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String iccid = BytesUtil.bin2HexStr(simCardNumByteArr);
// 运营商 0x00 移动 0x02 电信 0x03 联通 0x04 其他
startIndex += length;
length = 1;
byte[] businessTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String business = BytesUtil.bcd2Str(businessTypeByteArr);
LoginRequestData loginRequestData = LoginRequestData.builder()
.pileSn(pileSn)
.pileType(pileType)
.connectorNum(connectorNum)
.communicationVersion(communicationVersion)
.programVersion(programVersion)
.internetConnection(internetConnection)
.iccid(iccid)
.business(business)
.build();
// 结果(默认 0x01:登录失败)
byte[] flag = Constants.oneByteArray;
// 通过桩编码SN查询数据库如果有数据则登录成功否则登录失败
PileInfoVO pileInfoVO = null;
try {
pileInfoVO = pileBasicInfoService.selectPileInfoBySn(pileSn);
} catch (Exception e) {
log.error("selectPileInfoBySn发生异常", e);
}
if (pileInfoVO != null) {
flag = Constants.zeroByteArray;
// 登录成功保存桩号和channel的关系
PileChannelEntity.put(pileSn, channel);
// 更改桩和该桩下的枪口状态分别为 在线、空闲 公共方法修改状态
pileBasicInfoService.updateStatus(BytesUtil.bcd2Str(ykcDataProtocol.getFrameType()), pileSn, null, null, null);
}
// 充电桩使用的sim卡把信息存库
if (StringUtils.equals("00", internetConnection)) {
try {
pileBasicInfoService.updatePileSimInfo(pileSn, iccid);
} catch (Exception e) {
log.error("更新充电桩sim卡信息失败", e);
}
}
// 保存报文
String jsonMsg = JSONObject.toJSONString(loginRequestData);
pileMsgRecordService.save(pileSn, pileSn, type, jsonMsg, ykcDataProtocol.getHEXString());
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 对时
ProofreadTimeCommand command = ProofreadTimeCommand.builder().pileSn(pileSn).build();
ykcPushCommandService.pushProofreadTimeCommand(command);
});
// log.info("下面进行下发二维码 pileSn:{}, thread:{}", pileSn, Thread.currentThread().getName());
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 下发二维码
IssueQRCodeCommand issueQRCodeCommand = IssueQRCodeCommand.builder().pileSn(pileSn).build();
ykcPushCommandService.pushIssueQRCodeCommand(issueQRCodeCommand);
});
// 消息体
byte[] messageBody = Bytes.concat(pileSnByte, flag);
return getResult(ykcDataProtocol, messageBody);
}
}

View File

@@ -0,0 +1,45 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 离线卡数据清除
*
* 离线卡清除是平台主动下发的操作,平台在充电桩在线时会下发此数据帧到充电桩,充电桩接收到离线卡数据清除报文后清除到桩本地对应的离线卡数据
*
* @author JS-ZZA
* @date 2022/9/19 14:54
*/
@Slf4j
@Component
public class OfflineCardDataCleaningHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.OFFLINE_CARD_DATA_CLEANING_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===离线卡数据清除===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 清除离线卡的个数
// 第 1 个卡物理卡号
// 第 N 个卡物理卡号
return null;
}
}

View File

@@ -0,0 +1,69 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 离线卡数据清除应答
*
* @author JS-ZZA
* @date 2022/9/27 9:59
*/
@Slf4j
@Component
public class OfflineCardDataCleaningResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.OFFLINE_CARD_DATA_CLEANING_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===离线卡数据清除应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编码
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 第 1 个卡物理卡号
startIndex += length;
length = 8;
byte[] firstCardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 清除标记 0x00 清除失败 0x01 清除成功
startIndex += length;
length = 1;
byte[] cleanFlagByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 失败原因 0x01 卡号格式错误 0x02 清除成功
startIndex += length;
byte[] failedReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 第N个物理卡号
// 清除标记
// 失败原因
return null;
}
}

View File

@@ -0,0 +1,46 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 离线卡数据查询
*
* 离线卡数据查询由平台主动向桩发起的查询请求, 平台在充电桩在线时会按需下发此数据帧到充电桩,
* 桩接收到该报文后进行查询桩本地是否存在对应的离线卡
*
* @author JS-ZZA
* @date 2022/9/27 10:18
*/
@Slf4j
@Component
public class OfflineCardDataQueryHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.OFFLINE_CARD_DATA_QUERY_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===离线卡数据查询===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编码
// 查询的离线卡个数
// 第一个物理卡号
// 第N个物理卡号
return null;
}
}

View File

@@ -0,0 +1,60 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 离线卡数据查询应答
*
* @author JS-ZZA
* @date 2022/9/27 10:20
*/
@Slf4j
@Component
public class OfflineCardDataQueryResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.OFFLINE_CARD_DATA_QUERY_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===离线卡数据查询应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 第1个卡物理卡号
startIndex += length;
length = 8;
byte[] firstCardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 查询结果
startIndex += length;
length = 1;
byte[] firstCardResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 第N+1个卡物理卡号
// 查询结果
return null;
}
}

View File

@@ -0,0 +1,46 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 离线卡数据同步
*
* 离线卡适用于桩离线充电模式, 平台在充电桩在线时会下发此数据帧到充电桩, 充电桩接收到后储存离线卡信息到桩本地
* (如果已存在离线卡则用最新的数据覆盖本地数据, 不存在则插入), 若用户刷卡充电时桩处理离线模式,则刷鉴权走桩本地进行判断
*
* @author JS-ZZA
* @date 2022/9/19 14:51
*/
@Slf4j
@Component
public class OfflineCardDataSynchronizationHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.OFFLINE_CARD_DATA_SYNCHRONIZATION_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===离线卡数据同步===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 下发卡个数
// 第一个卡逻辑卡号
// 第N个卡物理卡号
return null;
}
}

View File

@@ -0,0 +1,57 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 离线卡数据同步应答
*
* @author JS-ZZA
* @date 2022/9/27 9:31
*/
@Slf4j
@Component
public class OfflineCardDataSynchronizationResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.OFFLINE_CARD_DATA_SYNCHRONIZATION_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===离线卡数据同步应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
//消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编码
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 保存结果 0x00 失败 0x01 成功
startIndex += length;
length = 1;
byte[] resultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 失败原因 0x01 卡号格式错误 0x02 储存空间不足
startIndex += length;
byte[] failedReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,129 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.IOrderBasicInfoService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 参数配置 Handler
*
* GBT-27930 充电桩与 BMS 参数配置阶段报文
* @author JS-ZZA
* @date 2022/9/19 13:24
*/
@Slf4j
@Component
public class ParameterConfigurationHandler extends AbstractHandler{
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.PARAMETER_CONFIGURATION_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===参数配置===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] serialNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(serialNumByteArr);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 单体动力蓄电池最高允许充电电压 0.01 V/位, 0 V 偏移量; 数据范围: 0~24 V
startIndex += length;
length = 2;
byte[] BMSMaxVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 最高允许充电电流 0.1 A/位, -400A 偏移量
startIndex += length;
byte[] BMSMaxCurrentByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 动力蓄电池标称总能量 0.1 kWh/位, 0 kWh 偏移量; 数据范围: 0~1000 kWh
startIndex += length;
byte[] BMSSumEnergyByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 最高允许充电总电压 0.1 V/位, 0 V 偏移量
startIndex += length;
byte[] BMSMaxChargingVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 最高允许温度 1ºC/位, -50 ºC 偏移量;数据范 围: -50 ºC ~+200 ºC
startIndex += length;
length = 1;
byte[] BMSMaxTemperatureByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// BMS 整车动力 蓄电池荷电状态(soc) 0.1%/位, 0%偏移量;数据范围: 0~100%
startIndex += length;
length = 2;
byte[] BMSSocByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String soc = YKCUtils.convertVoltageCurrent(BMSSocByteArr);
// BMS 整车动力蓄电池当前电池电压 整车动力蓄电池总电压
startIndex += length;
byte[] BMSRealTimeVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩最高输出电压 0.1 V /位, 0 V 偏移量
startIndex += length;
byte[] pileMaxOutputVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩最低输出电压 0.1 V /位, 0 V 偏移量
startIndex += length;
byte[] pileMinOutputVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩最大输出电流 0.1 A/位, -400 A 偏移量
startIndex += length;
byte[] pileMaxOutputCurrentByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 电桩最小输出电流 0.1 A/位, -400 A 偏移量
startIndex += length;
byte[] pileMinOutputCurrentByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
log.info("参数配置, 起始SOC:{}", soc);
// 查询该订单下信息将起始soc传入
OrderBasicInfo orderInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode);
if (Objects.nonNull(orderInfo)) {
OrderBasicInfo orderBasicInfo = OrderBasicInfo.builder()
.id(orderInfo.getId())
.startSOC(soc)
.build();
orderBasicInfoService.updateOrderBasicInfo(orderBasicInfo);
}
return null;
}
}

View File

@@ -0,0 +1,45 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电桩工作参数设置.
*
* 远程设置充电桩是否停用;设置充电桩允许输出功率,以实现电网功率的调节
*
* @author JS-ZZA
* @date 2022/9/19 15:06
*/
@Slf4j
@Component
public class PileWorkingParameterSettingHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGING_PILE_WORKING_PARAMETER_SETTING_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电桩工作参数设置===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 是否允许工作
// 充电桩最大允许输出功率
// return super.supplyProcess(ykcDataTemplate, channel);
return null;
}
}

View File

@@ -0,0 +1,52 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电桩工作参数设置应答
*
* @author JS-ZZA
* @date 2022/9/27 10:40
*/
@Slf4j
@Component
public class PileWorkingParameterSettingResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGING_PILE_WORKING_PARAMETER_SETTING_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电桩工作参数设置应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 设置结果
startIndex += length;
length = 1;
byte[] settingResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,57 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.netty.service.yunkuaichong.impl.YKCPushCommandServiceImpl;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 读取实时监测数据(服务器端向桩发送消息 0x12
*
* @deprecated 桩不会发送这个指令,由平台主动发送
* @author JS-ZZA
* @date 2022/9/19 8:43
*/
@Slf4j
@Component
public class ReadRealTimeMonitorDataHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.READ_REAL_TIME_MONITOR_DATA_CODE.getBytes());
@Autowired
private YKCPushCommandServiceImpl ykcPushBusinessServiceImpl;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===读取实时监测数据===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
byte[] pileSnByteArr = new byte[]{};
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 枪号
byte[] pileConnectorByteArr = Constants.oneByteArray;
// 拼接消息体
byte[] msg = Bytes.concat(pileSnByteArr, pileConnectorByteArr);
// push消息
// boolean result = ykcPushBusinessServiceImpl.push(msg, pileSn, YKCFrameTypeCode.READ_REAL_TIME_MONITOR_DATA_CODE);
// log.info(String.valueOf(result));
return null;
}
}

View File

@@ -0,0 +1,74 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 余额更新应答
*
* @author JS-ZZA
* @date 2022/9/19 14:47
*/
@Slf4j
@Component
public class RemoteAccountBalanceUpdateRequestHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_ACCOUNT_BALANCE_UPDATE_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===余额更新应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编码
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 物理卡号 不足 8 位补零
//如果不为零, 需要校验本次充电是 否为此卡充电
//如果为零, 则不校验, 直接更新桩 当前充电用户余额
startIndex += length;
length = 8;
byte[] cardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 修改后账户金额 保留两位小数
startIndex += length;
length = 4;
byte[] accountBalance = BytesUtil.copyBytes(msgBody, startIndex, length);
// 应答
// 帧类型
byte[] remoteAccountBalanceUpdateAnswerCodeBytes = YKCFrameTypeCode.REMOTE_ACCOUNT_BALANCE_UPDATE_ANSWER_CODE.getBytes();
// 修改结果 0x00-修改成功
//0x01-设备编号错误
//0x02-卡号错误
return null;
}
}

View File

@@ -0,0 +1,45 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 遥控地锁升锁与降锁命令
*
* 服务器下发命令给地锁,地锁执行动作
*
* @author JS-ZZA
* @date 2022/9/19 15:41
*/
@Slf4j
@Component
public class RemoteControlGroundLockHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_CONTROL_GROUND_LOCK_LIFTING_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===遥控地锁升锁与降锁命令===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编码
// 枪号
// 升/降地锁
// 预留位
// return super.supplyProcess(ykcDataTemplate, channel);
return null;
}
}

View File

@@ -0,0 +1,57 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 充电桩返回遥控地锁升锁与降锁数据(上行)
*
* @author JS-ZZA
* @date 2022/9/27 13:21
*/
@Slf4j
@Component
public class RemoteControlGroundLockResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.CHARGING_PILE_RESPOND_GROUND_LOCK_LIFTING_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===充电桩返回遥控地锁升锁与降锁数据(上行)===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 枪号
startIndex += length;
length = 1;
byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 地锁控制返回标志 布尔型( 1 鉴权成功; 0 鉴权失败)
startIndex += length;
byte[] controlResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// 预留位
startIndex += length;
length = 4;
byte[] waitingUseByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,75 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.netty.service.yunkuaichong.impl.YKCPushCommandServiceImpl;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 后台远程下发二维码前缀指令
*
* 二维码下发时,只需下发前缀,同时选择是第 0 种格式,还是第 1 种格式即可,如果二维码格式为 0 种,桩自动补充桩编号。
* 如果二维码格式为 1 种,桩自动补充桩编号+2位枪编号。
* 注册通过后,后台即可立即下发二维码。推荐每次注册通过后,均下发一次二维码。每个桩下发一次前缀即可。无须按照枪个数下发。
*
* @deprecated 桩不会发送这个指令,由平台主动发送
* @author JS-ZZA
* @date 2022/9/29 14:05
*/
@Slf4j
@Component
public class RemoteIssuedQrCodeHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_ISSUE_QRCODE_CODE.getBytes());
@Autowired
private YKCPushCommandServiceImpl ykcPushBusinessService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===后台远程下发二维码前缀指令===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编码
String pileSn = "88000000000001";
byte[] a = new byte[]{88};
byte[] b = Constants.zeroByteArray;
byte[] c = Constants.zeroByteArray;
byte[] d = Constants.zeroByteArray;
byte[] e = Constants.zeroByteArray;
byte[] f = Constants.zeroByteArray;
byte[] g = new byte[]{0x02};
byte[] pileSnByteArr = Bytes.concat(a, b, c, d, e, f, g);
// 二维码格式 0x00第一种 前缀+桩编号 0x01第二种 前缀+桩编号+枪编号
byte[] qrCodeTypeByteArr = Constants.zeroByteArray;
// 二维码前缀 如“www.baidu.comNo=”
String qrCodePrefix = "https://wx.charging.shbochong.cn/prepare_charge?code=";
byte[] qrCodePrefixByteArr = BytesUtil.str2Asc(qrCodePrefix);
// 二维码前缀长度 二维码前缀长度长度最大不超过200 字节
int length = qrCodePrefix.length();
byte[] qrCodePrefixLengthByteArr = BytesUtil.intToBytes(length);
// 拼接消息体
byte[] msg = Bytes.concat(pileSnByteArr, qrCodeTypeByteArr, qrCodePrefixLengthByteArr, qrCodePrefixByteArr);
// push消息
// boolean result = ykcPushBusinessService.push(msg, pileSn, YKCFrameTypeCode.REMOTE_ISSUE_QRCODE_CODE);
// log.info(String.valueOf(result));
return null;
}
}

View File

@@ -0,0 +1,52 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 桩应答远程下发二维码前缀指令
*
* @author JS-ZZA
* @date 2022/9/29 14:10
*/
@Slf4j
@Component
public class RemoteIssuedQrCodeResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_ISSUE_QRCODE_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===桩应答远程下发二维码前缀指令===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编码
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 下发结果 0x00成功 0x01失败
startIndex += length;
length = 1;
byte[] issuedResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,41 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 远程重启
*
* 重启充电桩,应对部分问题,如卡死
* 这个属于平台主动下发的指令
* @author JS-ZZA
* @date 2022/9/19 15:49
*/
@Slf4j
@Component
@Deprecated
public class RemoteRestartHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_RESTART_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===远程重启===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 执行控制 0x01立即执行 0x02空闲执行
return null;
}
}

View File

@@ -0,0 +1,61 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.service.IPileMsgRecordService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 远程重启应答
*
* @author JS-ZZA
* @date 2022/9/27 13:27
*/
@Slf4j
@Component
public class RemoteRestartResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_RESTART_ANSWER_CODE.getBytes());
@Autowired
private IPileMsgRecordService pileMsgRecordService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===远程重启应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 保存报文
String jsonMsg = JSONObject.toJSONString(ykcDataProtocol);
pileMsgRecordService.save(pileSn, null, type, jsonMsg, ykcDataProtocol.getHEXString());
// 设置结果
startIndex += length;
length = 1;
byte[] settingResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,98 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.enums.ykc.ChargingFailedReasonEnum;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.service.IOrderBasicInfoService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 远程启动充电命令回复 0x33, 0x34
*
* @author JS-ZZA
* @date 2022/9/19 14:35
*/
@Slf4j
@Component
public class RemoteStartChargingRequestHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_START_CHARGING_ANSWER_CODE.getBytes());
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===远程启动充电命令回复===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] orderCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(orderCodeByteArr);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr);
// 启动结果 0x00失败 0x01成功
startIndex += length;
byte[] startResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String startResult = BytesUtil.bcd2Str(startResultByteArr);
/**
* 失败原因
*
* 桩在收到启充命令后,检测到未插枪则发送 0x33 报文回复充电失败。
* 若在 60 秒(以收到 0x34 时间开始计算)内检测到枪重新连接,则补送 0x33 成功报文;超时或者离线等其他异常,桩不启充、不补发 0x33 报文
* 0x00 无
* 0x01 设备编号不匹配
* 0x02 枪已在充电
* 0x03 设备故障
* 0x04 设备离线
* 0x05 未插枪
*/
startIndex += length;
byte[] failedReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String failedReason = BytesUtil.bin2HexStr(failedReasonByteArr);
String failedReasonMsg = ChargingFailedReasonEnum.getMsgByCode(Integer.parseInt(failedReason, 16));
if (StringUtils.equals(startResult, Constants.DOUBLE_ZERO)) {
// 启动失败
orderBasicInfoService.chargingPileFailedToStart(orderCode, failedReasonMsg);
} else {
// 启动成功
orderBasicInfoService.chargingPileStartedSuccessfully(orderCode);
}
// orderBasicInfoService.updateOrderBasicInfo(orderInfo);
log.info("交易流水号:{}, 桩编码:{}, 枪号:{}, 启动结果:{}, 失败原因:{}", orderCode, pileSn, connectorCode, startResult, failedReasonMsg);
return null;
}
}

View File

@@ -0,0 +1,99 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.enums.ykc.OrderStatusEnum;
import com.jsowell.common.enums.ykc.StopChargingFailedReasonEnum;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.IOrderBasicInfoService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 远程停机命令回复
*
* @author JS-ZZA
* @date 2022/9/19 14:37
*/
@Slf4j
@Component
public class RemoteStopChargingRequestHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_STOP_CHARGING_ANSWER_CODE.getBytes());
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===远程停机命令回复===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr);
// 停止结果 0x00失败 0x01成功
startIndex += length;
byte[] stopResultByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String stopResult = BytesUtil.bcd2Str(stopResultByteArr);
/**
* 失败原因
* 0x00 无
* 0x01 设备编号不匹配
* 0x02 枪未处于充电状态
* 0x03 其他
*/
startIndex += length;
byte[] reasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String reasonCode = BytesUtil.bcd2Str(reasonByteArr);
String reason = StopChargingFailedReasonEnum.getMsgByCode(reasonCode);
// 通过桩编号+枪口号 查出订单
OrderBasicInfo order = orderBasicInfoService.queryChargingByPileSnAndConnectorCode(pileSn, connectorCode);
if (order != null) {
// 收到停机回复后,修改订单状态
if (StringUtils.equals(stopResult, "01")) {
// 停机成功,修改订单状态为 待结算
order.setOrderStatus(OrderStatusEnum.STAY_SETTLEMENT.getValue());
if (order.getChargeEndTime() == null) {
order.setChargeEndTime(new Date()); // 结束充电时间
}
} else {
// 停机失败,修改订单状态为 异常
order.setOrderStatus(OrderStatusEnum.ABNORMAL.getValue());
order.setReason(reason);
}
orderBasicInfoService.updateOrderBasicInfo(order);
}
return null;
}
}

View File

@@ -0,0 +1,58 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 远程更新
*
* 对桩进行软件升级,平台升级模式为 ftp 文件升级,由桩企提供升级需要的更新文件(特定文件名, 由桩企定义)
* 平台在数据帧中提供访问更新文件相关服务器地址及下载路径信息, 桩下载完更新程序后对文件进行较验,并对桩进行升级
*
* @author JS-ZZA
* @date 2022/9/19 15:56
*/
@Slf4j
@Component
public class RemoteUpdateHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_UPDATE_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===远程更新===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 桩型号 0x01直流 0x02交流
// 桩功率 不足 2 位补零
// 升级服务器地址 不足 16 位补零
// 升级服务器端口 不足 2 位补零
// 用户名 不足 16 位补零
// 密码 不足 16 位补零
// 文件路径 不足 32 位补零,文件路径名由平 台定义
// 执行控制 0x01立即执行 0x02空闲执行
// 下载超时时间 单位: min
return null;
}
}

View File

@@ -0,0 +1,52 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 远程更新应答
*
* @author JS-ZZA
* @date 2022/9/27 13:32
*/
@Slf4j
@Component
public class RemoteUpdateResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_UPDATE_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[====远程更新应答====] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, 0, 7);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 升级状态 0x00-成功 0x01-编号错误 0x02-程序与桩型号不符 0x03-下载更新文件超时
startIndex += length;
length = 1;
byte[] updateStatusByteArr = BytesUtil.copyBytes(msgBody, 7, 1);
return null;
}
}

View File

@@ -0,0 +1,41 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 对时设置
*
* 运营平台同步充电桩时钟,以保证充电桩与运营平台的时钟一致
*
* @author JS-ZZA
* @date 2022/9/19 15:11
*/
@Slf4j
@Component
public class TimeCheckSettingHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.TIME_CHECK_SETTING_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===对时设置===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 下发
// 桩编号
// 当前时间 CP56Time2a 格式
// return super.supplyProcess(ykcDataTemplate, channel);
return null;
}
}

View File

@@ -0,0 +1,52 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 对时设置应答
*
* @author JS-ZZA
* @date 2022/9/27 11:09
*/
@Slf4j
@Component
public class TimeCheckSettingResponseHandler extends AbstractHandler{
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.TIME_CHECK_SETTING_ANSWER_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
}
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===对时设置应答===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 7;
// 桩编号
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 保存时间
saveLastTime(pileSn);
// 当前时间
startIndex += length;
length = 7;
byte[] currentTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
return null;
}
}

View File

@@ -0,0 +1,559 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.TransactionRecordsData;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.enums.ykc.OrderStatusEnum;
import com.jsowell.common.enums.ykc.YKCChargingStopReasonEnum;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.Cp56Time2a.Cp56Time2aUtil;
import com.jsowell.common.util.DateUtils;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.common.util.id.IdUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.IOrderBasicInfoService;
import com.jsowell.pile.service.IPileMsgRecordService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Objects;
/**
* 交易记录确认
* 这一帧仅是报文交互使用, 意指平台成功接收到交易记录报文,并不代表交易订单成功结算
* 运营平台接收到结算账单上传后,都需回复此确认信息。若桩未收到回复帧,则 5 分钟后继续 上送一次交易记录,
* 此情况下无论平台是否成功回复都停止上送。
*
* @author JS-ZZA
* @date 2022/9/19 14:40
*/
@Slf4j
@Component
public class TransactionRecordsRequestHandler extends AbstractHandler {
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.TRANSACTION_RECORDS_CODE.getBytes());
private final String oldVersionType = YKCUtils.frameType2Str(YKCFrameTypeCode.TRANSACTION_RECORDS_OLD_VERSION_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
YKCOperateFactory.register(oldVersionType, this);
}
@Autowired
private RedisCache redisCache;
@Autowired
private IPileMsgRecordService pileMsgRecordService;
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
/*public static void main(String[] args) {
String msgBodyStr = "880000000000040122121516483531998800000000000401000030100f0c16a8003b011a0368100f0400000000000000000000000000c891050000000000000000000000000080140700a406000000000000d01e000090170d0000000000000000000000000010b0390b0078f2390b00a406000000000000781e0000ffffffffffffffffffffffffffffffffff01a8003b011a0368830000000000000000" ;
byte[] msgBody = BytesUtil.str2Bcd(msgBodyStr);
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] orderCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(orderCodeByteArr);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 枪号
startIndex += length;
length = 1;
byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr);
// 根据不同程序版本获取工具类
String programVersion = redisCache.getCacheMapValue(CacheConstants.PILE_PROGRAM_VERSION, pileSn);
AbsCp56Time2aUtil cp56Time2aUtil = Cp56Time2aFactory.getInvokeStrategy(programVersion);
// 开始时间 CP56Time2a 格式
startIndex += length;
length = 7;
byte[] startTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String startTime = cp56Time2aUtil.toDateString(startTimeByteArr);
// 结束时间 CP56Time2a 格式
startIndex += length;
byte[] endTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String endTime = cp56Time2aUtil.toDateString(endTimeByteArr);
// 尖单价 精确到小数点后五位(尖电费+尖服务费,见费率帧)
startIndex += length;
length = 4;
byte[] sharpPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpPrice = YKCUtils.convertDecimalPoint(sharpPriceByteArr, 5);
// 尖电量 精确到小数点后四位
startIndex += length;
length = 4;
byte[] sharpUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpUsedElectricity = YKCUtils.convertDecimalPoint(sharpUsedElectricityByteArr, 4);
// 计损尖电量
startIndex += length;
byte[] sharpPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpPlanLossElectric = YKCUtils.convertDecimalPoint(sharpPlanLossElectricityByteArr, 4);
// 尖金额
startIndex += length;
byte[] sharpAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpAmount = YKCUtils.convertDecimalPoint(sharpAmountByteArr, 4);
// 峰单价 精确到小数点后五位(峰电费+峰服务费)
startIndex += length;
byte[] peakPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakPrice = YKCUtils.convertDecimalPoint(peakPriceByteArr, 5);
// 峰电量
startIndex += length;
byte[] peakUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakUsedElectricity = YKCUtils.convertDecimalPoint(peakUsedElectricityByteArr, 4);
// 计损峰电量
startIndex += length;
byte[] peakPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakPlanLossElectricity = YKCUtils.convertDecimalPoint(peakPlanLossElectricityByteArr, 4);
// 峰金额
startIndex += length;
byte[] peakAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakAmount = YKCUtils.convertDecimalPoint(peakAmountByteArr, 4);
// 平单价 精确到小数点后五位(平电费+平服务费)
startIndex += length;
byte[] flatPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatPrice = YKCUtils.convertDecimalPoint(flatPriceByteArr, 5);
// 平电量
startIndex += length;
byte[] flatUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatUsedElectricity = YKCUtils.convertDecimalPoint(flatUsedElectricityByteArr, 4);
// 计损平电量
startIndex += length;
byte[] flatPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatPlanLossElectricity = YKCUtils.convertDecimalPoint(flatPlanLossElectricityByteArr, 4);
// 平金额
startIndex += length;
byte[] flatAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatAmount = YKCUtils.convertDecimalPoint(flatAmountByteArr, 4);
// 谷单价 精确到小数点后五位(谷电费+谷 服务费)
startIndex += length;
byte[] valleyPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyPrice = YKCUtils.convertDecimalPoint(valleyPriceByteArr, 5);
// 谷电量
startIndex += length;
byte[] valleyUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyUsedElectricity = YKCUtils.convertDecimalPoint(valleyUsedElectricityByteArr, 4);
// 计损谷电量
startIndex += length;
byte[] valleyPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyPlanLossElectricity = YKCUtils.convertDecimalPoint(valleyPlanLossElectricityByteArr, 4);
// 谷金额
startIndex += length;
byte[] valleyAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyAmount = YKCUtils.convertDecimalPoint(valleyAmountByteArr, 4);
// 电表总起值
startIndex += length;
length = 5;
byte[] ammeterTotalStartByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String ammeterTotalStart = YKCUtils.convertDecimalPoint(ammeterTotalStartByteArr, 4);
// 电表总止值
startIndex += length;
byte[] ammeterTotalEndByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String ammeterTotalEnd = YKCUtils.convertDecimalPoint(ammeterTotalEndByteArr, 4);
// 总电量
startIndex += length;
length = 4;
byte[] totalElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String totalElectricity = YKCUtils.convertDecimalPoint(totalElectricityByteArr, 4);
// 计损总电量
startIndex += length;
byte[] planLossTotalElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String planLossTotalElectricity = YKCUtils.convertDecimalPoint(planLossTotalElectricityByteArr, 4);
// 消费金额 精确到小数点后四位,包含电费、 服务费
startIndex += length;
byte[] consumptionAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String consumptionAmount = YKCUtils.convertDecimalPoint(consumptionAmountByteArr, 4);
// VIN 码 VIN 码,此处 VIN 码和充电时 VIN 码不同, 正序直接上传, 无需补 0 和反序
startIndex += length;
length = 17;
byte[] vinCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String vinCode = BytesUtil.ascii2Str(vinCodeByteArr);
*//**
* 交易标识
* 0x01 app 启动
* 0x02卡启动
* 0x04离线卡启动
* 0x05: vin 码启动充电
*//*
startIndex += length;
length = 1;
byte[] transactionIdentifierByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String transactionIdentifier = BytesUtil.bcd2Str(transactionIdentifierByteArr);
// 交易时间 CP56Time2a 格式
startIndex += length;
length = 7;
byte[] transactionTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String transactionTime = cp56Time2aUtil.toDateString(transactionTimeByteArr);
// 停止原因
startIndex += length;
length = 1;
byte[] stopReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String stopReason = BytesUtil.bin2HexStr(stopReasonByteArr);
String stopReasonMsg = YKCChargingStopReasonEnum.getMsgByCode(Integer.parseInt(stopReason, 16));
// 物理卡号 不足 8 位补 0
startIndex += length;
length = 8;
byte[] cardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
byte[] logicCardNum = BytesUtil.checkLengthAndBehindAppendZero(cardNumByteArr, 16);
String logicCard = BytesUtil.binary(logicCardNum, 10);
log.info("交易流水号:{}, 桩编号:{}, 枪号:{}, 开始时间:{}, 结束时间:{}, 尖单价:{}, 尖电量:{}, 计损尖电量:{}, 尖金额:{}, " +
"峰单价:{}, 峰电量:{}, 计损峰电量:{}, 峰金额:{}, 平单价:{}, 平电量:{}, 计损平电量:{}, 平金额:{}, " +
"谷单价:{}, 谷电量:{}, 计损谷电量:{}, 谷金额:{}, 电表总起值:{}, 电表总止值:{}, 总电量:{}, 计损总电量:{}, 消费金额:{}, " +
"电动汽车唯一标识:{}, 交易标识:{}, 交易时间:{}, 停止原因码:{}, 停止原因描述:{}, 物理卡号:{}",
orderCode, pileSn, connectorCode, startTime, endTime, sharpPrice, sharpUsedElectricity, sharpPlanLossElectric, sharpAmount,
peakPrice, peakUsedElectricity, peakPlanLossElectricity, peakAmount, flatPrice, flatUsedElectricity, flatPlanLossElectricity, flatAmount,
valleyPrice, valleyUsedElectricity, valleyPlanLossElectricity, valleyAmount, ammeterTotalStart, ammeterTotalEnd, totalElectricity, planLossTotalElectricity,
consumptionAmount, vinCode, transactionIdentifier, transactionTime, stopReason, stopReasonMsg, logicCard);
}*/
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
// log.info("[===交易记录===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] orderCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(orderCodeByteArr);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
// 枪号
startIndex += length;
length = 1;
byte[] connectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(connectorCodeByteArr);
// 开始时间 CP56Time2a 格式
startIndex += length;
length = 7;
byte[] startTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
// String binary = BytesUtil.binary(startTimeByteArr, 16);
Date startDate = Cp56Time2aUtil.byte2Hdate(startTimeByteArr);
String startTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, startDate);
// 结束时间 CP56Time2a 格式
startIndex += length;
byte[] endTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
Date endDate = Cp56Time2aUtil.byte2Hdate(endTimeByteArr);
String endTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, endDate);
// 尖单价 精确到小数点后五位(尖电费+尖服务费,见费率帧)
startIndex += length;
length = 4;
byte[] sharpPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpPrice = YKCUtils.convertDecimalPoint(sharpPriceByteArr, 5);
// 尖电量 精确到小数点后四位
startIndex += length;
length = 4;
byte[] sharpUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpUsedElectricity = YKCUtils.convertDecimalPoint(sharpUsedElectricityByteArr, 4);
// 计损尖电量
startIndex += length;
byte[] sharpPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpPlanLossElectricity = YKCUtils.convertDecimalPoint(sharpPlanLossElectricityByteArr, 4);
// 尖金额
startIndex += length;
byte[] sharpAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String sharpAmount = YKCUtils.convertDecimalPoint(sharpAmountByteArr, 4);
// 峰单价 精确到小数点后五位(峰电费+峰服务费)
startIndex += length;
byte[] peakPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakPrice = YKCUtils.convertDecimalPoint(peakPriceByteArr, 5);
// 峰电量
startIndex += length;
byte[] peakUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakUsedElectricity = YKCUtils.convertDecimalPoint(peakUsedElectricityByteArr, 4);
// 计损峰电量
startIndex += length;
byte[] peakPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakPlanLossElectricity = YKCUtils.convertDecimalPoint(peakPlanLossElectricityByteArr, 4);
// 峰金额
startIndex += length;
byte[] peakAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String peakAmount = YKCUtils.convertDecimalPoint(peakAmountByteArr, 4);
// 平单价 精确到小数点后五位(平电费+平服务费)
startIndex += length;
byte[] flatPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatPrice = YKCUtils.convertDecimalPoint(flatPriceByteArr, 5);
// 平电量
startIndex += length;
byte[] flatUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatUsedElectricity = YKCUtils.convertDecimalPoint(flatUsedElectricityByteArr, 4);
// 计损平电量
startIndex += length;
byte[] flatPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatPlanLossElectricity = YKCUtils.convertDecimalPoint(flatPlanLossElectricityByteArr, 4);
// 平金额
startIndex += length;
byte[] flatAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String flatAmount = YKCUtils.convertDecimalPoint(flatAmountByteArr, 4);
// 谷单价 精确到小数点后五位(谷电费+谷 服务费)
startIndex += length;
byte[] valleyPriceByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyPrice = YKCUtils.convertDecimalPoint(valleyPriceByteArr, 5);
// 谷电量
startIndex += length;
byte[] valleyUsedElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyUsedElectricity = YKCUtils.convertDecimalPoint(valleyUsedElectricityByteArr, 4);
// 计损谷电量
startIndex += length;
byte[] valleyPlanLossElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyPlanLossElectricity = YKCUtils.convertDecimalPoint(valleyPlanLossElectricityByteArr, 4);
// 谷金额
startIndex += length;
byte[] valleyAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String valleyAmount = YKCUtils.convertDecimalPoint(valleyAmountByteArr, 4);
// 电表总起值
startIndex += length;
length = 5;
byte[] ammeterTotalStartByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String ammeterTotalStart = YKCUtils.convertDecimalPoint(ammeterTotalStartByteArr, 4);
// 电表总止值
startIndex += length;
byte[] ammeterTotalEndByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String ammeterTotalEnd = YKCUtils.convertDecimalPoint(ammeterTotalEndByteArr, 4);
// 总电量
startIndex += length;
length = 4;
byte[] totalElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String totalElectricity = YKCUtils.convertDecimalPoint(totalElectricityByteArr, 4);
// 计损总电量
startIndex += length;
byte[] planLossTotalElectricityByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String planLossTotalElectricity = YKCUtils.convertDecimalPoint(planLossTotalElectricityByteArr, 4);
// 消费金额 精确到小数点后四位,包含电费、 服务费
startIndex += length;
byte[] consumptionAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String consumptionAmount = YKCUtils.convertDecimalPoint(consumptionAmountByteArr, 4);
// VIN 码 VIN 码,此处 VIN 码和充电时 VIN 码不同, 正序直接上传, 无需补 0 和反序
startIndex += length;
length = 17;
byte[] vinCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String vinCode = BytesUtil.ascii2Str(vinCodeByteArr);
/**
* 交易标识
* 0x01 app 启动
* 0x02卡启动
* 0x04离线卡启动
* 0x05: vin 码启动充电
*/
startIndex += length;
length = 1;
byte[] transactionIdentifierByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String transactionIdentifier = BytesUtil.bcd2Str(transactionIdentifierByteArr);
// 交易时间 CP56Time2a 格式
startIndex += length;
length = 7;
byte[] transactionTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
Date transactionDate = Cp56Time2aUtil.byte2Hdate(transactionTimeByteArr);
String transactionTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, transactionDate);
// 停止原因
startIndex += length;
length = 1;
byte[] stopReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String stopReason = BytesUtil.bin2HexStr(stopReasonByteArr);
String stopReasonMsg = YKCChargingStopReasonEnum.getMsgByCode(Integer.parseInt(stopReason, 16));
// 物理卡号 不足 8 位补 0
startIndex += length;
length = 8;
byte[] cardNumByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
byte[] logicCardNum = BytesUtil.checkLengthAndBehindAppendZero(cardNumByteArr, 16);
String logicCard = BytesUtil.binary(logicCardNum, 10);
log.info("[===交易记录===]交易流水号:{}, 桩编号:{}, 枪号:{}, 开始时间:{}, 结束时间:{}, 尖单价:{}, 尖电量:{}, 计损尖电量:{}, 尖金额:{}, " +
"峰单价:{}, 峰电量:{}, 计损峰电量:{}, 峰金额:{}, 平单价:{}, 平电量:{}, 计损平电量:{}, 平金额:{}, " +
"谷单价:{}, 谷电量:{}, 计损谷电量:{}, 谷金额:{}, 电表总起值:{}, 电表总止值:{}, 总电量:{}, 计损总电量:{}, 消费金额:{}, " +
"电动汽车唯一标识:{}, 交易标识:{}, 交易日期、时间:{}, 停止原因码:{}, 停止原因描述:{}, 物理卡号:{}",
orderCode, pileSn, connectorCode, startTime, endTime, sharpPrice, sharpUsedElectricity, sharpPlanLossElectricity, sharpAmount,
peakPrice, peakUsedElectricity, peakPlanLossElectricity, peakAmount, flatPrice, flatUsedElectricity, flatPlanLossElectricity, flatAmount,
valleyPrice, valleyUsedElectricity, valleyPlanLossElectricity, valleyAmount, ammeterTotalStart, ammeterTotalEnd, totalElectricity, planLossTotalElectricity,
consumptionAmount, vinCode, transactionIdentifier, transactionTime, stopReason, stopReasonMsg, logicCard);
// 交易记录封装到对象里
TransactionRecordsData data = TransactionRecordsData.builder()
.orderCode(orderCode)
.pileSn(pileSn)
.connectorCode(connectorCode)
.startTime(startTime)
.endTime(endTime)
.sharpPrice(sharpPrice)
.sharpUsedElectricity(sharpUsedElectricity)
.sharpPlanLossElectricity(sharpPlanLossElectricity)
.sharpAmount(sharpAmount)
.peakPrice(peakPrice)
.peakUsedElectricity(peakUsedElectricity)
.peakPlanLossElectricity(peakPlanLossElectricity)
.peakAmount(peakAmount)
.flatPrice(flatPrice)
.flatUsedElectricity(flatUsedElectricity)
.flatPlanLossElectricity(flatPlanLossElectricity)
.flatAmount(flatAmount)
.valleyPrice(valleyPrice)
.valleyUsedElectricity(valleyUsedElectricity)
.valleyPlanLossElectricity(valleyPlanLossElectricity)
.valleyAmount(valleyAmount)
.ammeterTotalStart(ammeterTotalStart)
.ammeterTotalEnd(ammeterTotalEnd)
.totalElectricity(totalElectricity)
.planLossTotalElectricity(planLossTotalElectricity)
.consumptionAmount(consumptionAmount)
.vinCode(vinCode)
.transactionIdentifier(transactionIdentifier)
.transactionTime(transactionTime)
.stopReasonMsg(stopReasonMsg)
.logicCard(logicCard)
.build();
// 保存报文
String jsonMsg = JSONObject.toJSONString(data);
pileMsgRecordService.save(pileSn, pileSn + connectorCode, type, jsonMsg, ykcDataProtocol.getHEXString());
// 处理订单加锁
String lockKey = "settle_order_" + orderCode;
String uuid = IdUtils.fastUUID();
try {
// redis锁
Boolean isLock = redisCache.lock(lockKey, uuid, 1500);
if (isLock) {
processOrder(data);
}
} catch (Exception e) {
log.error("处理订单发生异常", e);
} finally {
if (uuid.equals(redisCache.getCacheObject(lockKey).toString())) {
redisCache.unLock(lockKey);
}
}
// TODO 将开始时间和结束时间存入订单表中
// OrderBasicInfo orderBasicInfo = OrderBasicInfo.builder()
// .orderCode(orderCode)
// .chargeStartTime()
// .chargeEndTime()
// .build()
/*
应答
确认结果 0x00 上传成功 0x01 非法账单
2022年12月15日11点28分发现返回 01非法账单充电桩会持续上传交易记录后面产生的交易记录被阻塞
*/
byte[] confirmResultBytes = Constants.zeroByteArray;
byte[] concatMsgBody = Bytes.concat(orderCodeByteArr, confirmResultBytes);
return getResult(ykcDataProtocol, concatMsgBody);
}
private void processOrder(TransactionRecordsData data) {
String orderCode = data.getOrderCode();
// 根据订单号查询订单信息
OrderBasicInfo orderBasicInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode);
if (orderBasicInfo != null) {
// 平台存在订单
orderBasicInfo.setReason(data.getStopReasonMsg());
// 如果订单状态为 异常,则改为 待结算
if (StringUtils.equals(OrderStatusEnum.ABNORMAL.getValue(), orderBasicInfo.getOrderStatus())) {
orderBasicInfo.setOrderStatus(OrderStatusEnum.STAY_SETTLEMENT.getValue());
}
// 校验一下开始时间和结束时间,防止充电中桩离线,时间不准确
if (Objects.isNull(orderBasicInfo.getChargeStartTime())) { // 开始时间
orderBasicInfo.setChargeStartTime(DateUtils.parseDate(data.getStartTime()));
}
if (Objects.isNull(orderBasicInfo.getChargeEndTime())) { // 结束时间
orderBasicInfo.setChargeEndTime(DateUtils.parseDate(data.getEndTime()));
}
orderBasicInfoService.updateOrderBasicInfo(orderBasicInfo);
// 结算订单操作
try {
orderBasicInfoService.settleOrder(data, orderBasicInfo);
} catch (Exception e) {
log.error("结算订单发生异常", e);
}
} else {
// 平台没有查到订单
orderBasicInfoService.saveAbnormalOrder(data);
log.warn("充电桩传来的交易记录,根据订单号:{}查询不到订单,判定为非法账单", orderCode);
}
}
}

View File

@@ -0,0 +1,267 @@
package com.jsowell.netty.handler;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.core.domain.ykc.RealTimeMonitorData;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.enums.ykc.OrderStatusEnum;
import com.jsowell.common.enums.ykc.YKCPileFaultReasonEnum;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.IOrderBasicInfoService;
import com.jsowell.pile.service.IPileBasicInfoService;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Objects;
/**
* 获取桩上传的实时监测数据
*
* @author JS-ZZA
* @date 2022/9/19 9:08
*/
@Slf4j
@Component
public class UploadRealTimeMonitorHandler extends AbstractHandler {
private final String type = YKCUtils.frameType2Str(YKCFrameTypeCode.UPLOAD_REAL_TIME_MONITOR_DATA_CODE.getBytes());
private final String oldVersionType = YKCUtils.frameType2Str(YKCFrameTypeCode.UPLOAD_REAL_TIME_MONITOR_DATA_OLD_VERSION_CODE.getBytes());
@Override
public void afterPropertiesSet() throws Exception {
YKCOperateFactory.register(type, this);
YKCOperateFactory.register(oldVersionType, this);
}
@Autowired
private IPileBasicInfoService pileBasicInfoService;
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
@Override
public byte[] supplyProcess(YKCDataProtocol ykcDataProtocol, Channel channel) {
log.info("[===获取桩上传的实时监测数据===] param:{}, channel:{}", JSONObject.toJSONString(ykcDataProtocol), channel.toString());
RealTimeMonitorData realTimeMonitorData = new RealTimeMonitorData();
// 获取消息体
byte[] msgBody = ykcDataProtocol.getMsgBody();
// log.info("上传实时数据msgBody:{}", BytesUtil.bcd2Str(msgBody));
int startIndex = 0;
int length = 16;
// 交易流水号
byte[] orderCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String orderCode = BytesUtil.bcd2Str(orderCodeByteArr);
realTimeMonitorData.setOrderCode(orderCode);
// 桩编码
startIndex += length;
length = 7;
byte[] pileSnByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String pileSn = BytesUtil.bcd2Str(pileSnByteArr);
realTimeMonitorData.setPileSn(pileSn);
// 保存时间
saveLastTime(pileSn);
// 枪号
startIndex += length;
length = 1;
byte[] pileConnectorCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorCode = BytesUtil.bcd2Str(pileConnectorCodeByteArr);
realTimeMonitorData.setConnectorCode(connectorCode);
// 枪口状态 0x00:离线 0x01:故障 0x02:空闲 0x03:充电
startIndex += length;
length = 1;
byte[] connectorStatusByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String connectorStatus = BytesUtil.bcd2Str(connectorStatusByteArr);
realTimeMonitorData.setConnectorStatus(connectorStatus);
// 是否归位 0x00:否 0x01:是 0x02:未知(无法检测到枪是否插回枪座即 未知)
startIndex += length;
length = 1;
byte[] homingFlagByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String homingFlag = BytesUtil.bcd2Str(homingFlagByteArr);
realTimeMonitorData.setHomingFlag(homingFlag);
// 是否插枪 0x00:否 0x01:是
startIndex += length;
length = 1;
byte[] putGunTypeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String putGunType = BytesUtil.bcd2Str(putGunTypeByteArr);
realTimeMonitorData.setPutGunType(putGunType);
// 输出电压 精确到小数点后一位;待机置零
startIndex += length;
length = 2;
byte[] outputVoltageByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String outputVoltage = YKCUtils.convertVoltageCurrent(outputVoltageByteArr);
realTimeMonitorData.setOutputVoltage(outputVoltage);
// 输出电流 精确到小数点后一位;待机置零
startIndex += length;
length = 2;
byte[] outputCurrentByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String outputCurrent = YKCUtils.convertVoltageCurrent(outputCurrentByteArr);
realTimeMonitorData.setOutputCurrent(outputCurrent);
// 枪线温度 整形, 偏移量-50待机置零
startIndex += length;
length = 1;
byte[] gunLineTemperatureByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String gunLineTemperature = String.valueOf(gunLineTemperatureByteArr[0]);
realTimeMonitorData.setGunLineTemperature(gunLineTemperature);
// 枪线编码 没有置零
startIndex += length;
length = 8;
byte[] gunLineCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String gunLineCode = BytesUtil.bcd2Str(gunLineCodeByteArr);
realTimeMonitorData.setGunLineCode(gunLineCode);
// SOC 待机置零;交流桩置零
startIndex += length;
length = 1;
byte[] SOCByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String SOC = String.valueOf(SOCByteArr[0]);
realTimeMonitorData.setSOC(SOC);
// 电池组最高温度 整形, 偏移量-50 ºC待机置零 交流桩置零
startIndex += length;
length = 1;
byte[] batteryMaxTemperatureByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String batteryMaxTemperature = String.valueOf(batteryMaxTemperatureByteArr[0]);
realTimeMonitorData.setBatteryMaxTemperature(batteryMaxTemperature);
// 累计充电时间 单位: min待机置零
startIndex += length;
length = 2;
byte[] sumChargingTimeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
int sumChargingTime = BytesUtil.bytesToIntLittle(sumChargingTimeByteArr);
realTimeMonitorData.setSumChargingTime(String.valueOf(sumChargingTime));
// 剩余时间 单位: min待机置零、交流桩置零
startIndex += length;
length = 2;
byte[] timeRemainingByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
int timeRemaining = BytesUtil.bytesToIntLittle(timeRemainingByteArr);
realTimeMonitorData.setTimeRemaining(String.valueOf(timeRemaining));
// 充电度数 精确到小数点后四位;待机置零
startIndex += length;
length = 4;
byte[] chargingDegreeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String chargingDegree = YKCUtils.convertDecimalPoint(chargingDegreeByteArr, 4);
realTimeMonitorData.setChargingDegree(chargingDegree);
// 计损充电度数 精确到小数点后四位;待机置零 未设置计损比例时等于充电度数
startIndex += length;
length = 4;
byte[] lossDegreeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String lossDegree = YKCUtils.convertDecimalPoint(lossDegreeByteArr, 4);
realTimeMonitorData.setLossDegree(lossDegree);
// 已充金额 精确到小数点后四位;待机置零 (电费+服务费) *计损充电度数
startIndex += length;
length = 4;
byte[] chargingAmountByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String chargingAmount = YKCUtils.convertDecimalPoint(chargingAmountByteArr, 4);
realTimeMonitorData.setChargingAmount(chargingAmount);
/**
* 硬件故障
*
* Bit 位表示0 否 1 是), 低位到高位顺序
* Bit1:急停按钮动作故障;
* Bit2:无可用整流模块;
* Bit3:出风口温度过高;
* Bit4:交流防雷故障;
* Bit5:交直流模块 DC20 通信中断;
* Bit6:绝缘检测模块 FC08 通信中断;
* Bit7:电度表通信中断;
* Bit8:读卡器通信中断;
* Bit9: RC10 通信中断;
* Bit10:风扇调速板故障;
* Bit11:直流熔断器故障;
* Bit12:高压接触器故障;
* Bit13:门打开;
*/
startIndex += length;
length = 2;
byte[] hardwareFaultTempByteArr = BytesUtil.copyBytes(msgBody, startIndex, length);
String hardwareFaultTemp = BytesUtil.bcd2Str(hardwareFaultTempByteArr);
String faultReason = "";
if (!StringUtils.equals(hardwareFaultTemp, "0000")) {
// 不等于0000说明有故障
StringBuffer sb = new StringBuffer(hardwareFaultTemp);
String lowOrder = sb.substring(0, 2);
String highOrder = sb.substring(2, 4);
// String hardwareFault = highOrder + lowOrder;
byte[] hardwareFaultByteArr = BytesUtil.str2Bcd(highOrder + lowOrder);
String binStr = BytesUtil.bytes2BinStr(hardwareFaultByteArr);
// log.info("binStr:{}", binStr); // 0000 0000 0000 0001
int faultCode = 0;
for (int i = 0; i < binStr.length(); i++) {
if (binStr.charAt(i) == '1') {
faultCode = 15 - i;
break;
}
}
faultReason = YKCPileFaultReasonEnum.getValueByCode(faultCode);
// log.info("故障码:{}, 故障原因:{}", faultCode, faultReason);
}
realTimeMonitorData.setHardwareFault(hardwareFaultTemp);
if (!StringUtils.equals(connectorStatus, "02")) {
log.info("0x13上传实时监测数据==交易流水号:{}, 桩编号:{}, 枪号:{}, 状态:{}, 枪是否归位:{}, 是否插枪:{}, 输出电压:{}, 输出电流:{}, 枪线温度:{}, " +
"枪线编码:{}, SOC:{}, 电池组最高温度:{}, 累计充电时间:{}, 剩余时间:{}, 充电度数:{}, 记损充电度数:{}, 已充金额:{}, " +
"硬件故障:{}, 故障码转换结果:{}", orderCode, pileSn, connectorCode, connectorStatus, homingFlag, putGunType, outputVoltage,
outputCurrent, gunLineTemperature, gunLineCode, SOC, batteryMaxTemperature, sumChargingTime, timeRemaining,
chargingDegree, lossDegree, chargingAmount, hardwareFaultTemp, faultReason
);
}
// 公共方法修改状态
pileBasicInfoService.updateStatus(BytesUtil.bcd2Str(ykcDataProtocol.getFrameType()), pileSn, connectorCode, connectorStatus, putGunType);
// 03表示充电中
if (StringUtils.equals(connectorStatus, "03")) {
// 充电时保存实时数据到redis
pileBasicInfoService.saveRealTimeMonitorData2Redis(realTimeMonitorData);
// 查询数据库中该订单当前信息
OrderBasicInfo orderInfo = orderBasicInfoService.getOrderInfoByOrderCode(orderCode);
if (Objects.nonNull(orderInfo)) {
boolean updateFlag = false;
if (StringUtils.equals(orderInfo.getOrderStatus(), OrderStatusEnum.NOT_START.getValue())
|| StringUtils.equals(orderInfo.getOrderStatus(), OrderStatusEnum.ABNORMAL.getValue())) {
updateFlag = true;
// 如果是未启动状态或者异常状态, 修改这个订单状态为充电中
orderInfo.setOrderStatus(OrderStatusEnum.IN_THE_CHARGING.getValue());
}
// 如果原来没有开始充电时间就保存当前时间为开始充电时间
if (orderInfo.getChargeStartTime() == null) {
updateFlag = true;
orderInfo.setChargeStartTime(new Date());
}
if (updateFlag) {
orderBasicInfoService.updateOrderBasicInfo(orderInfo);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
package com.jsowell.netty.server.yunkuaichong;
import com.jsowell.common.constant.Constants;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
@Slf4j
@Component
public class NettyServer implements CommandLineRunner {
@Resource
private NettyServerChannelInitializer nettyServerChannelInitializer;
@Order(value = 1)
@Override
public void run(String... args) throws Exception {
InetSocketAddress address = new InetSocketAddress(Constants.SOCKET_IP, Constants.SOCKET_PORT);
this.start(address);
}
public void start(InetSocketAddress address) {
// log.info("========NettyServer.start order 1");
//配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap() // //启动NIO服务的辅助启动类
.group(bossGroup, workerGroup) // 绑定线程池
.channel(NioServerSocketChannel.class) // 启动服务时, 通过反射创建一个NioServerSocketChannel对象
/*
===> 服务器初始化时执行, 属于AbstracBootstrap的方法
*/
.handler(new LoggingHandler(LogLevel.DEBUG)) // handler在初始化时就会执行可以设置打印日志级别
// 设置tcp缓冲区, 可连接队列大小
.option(ChannelOption.SO_BACKLOG, 128) //服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝
.option(ChannelOption.SO_REUSEADDR, true) //允许重复使用本地地址和端口
/*
===> 客户端连接成功之后执行, 属于ServerBootstrap的方法,继承自AbstractBootstrap
*/
.childOption(ChannelOption.SO_KEEPALIVE, true) //两小时没有数据通信时, 启用心跳保活机制探测客户端是否连接有效
.childOption(ChannelOption.SO_REUSEADDR, true)
.childHandler(nettyServerChannelInitializer)//编码解码
// 地址
.localAddress(address);
// 绑定端口,开始接收进来的连接
ChannelFuture future = bootstrap.bind(address.getPort()).sync();
if (future.isSuccess()) {
log.info("NettyServer启动成功, 开始监听端口:{}", address.getPort());
} else {
log.error("NettyServer启动失败", future.cause());
}
//关闭channel和块直到它被关闭
future.channel().closeFuture().sync();
} catch (Exception e) {
log.error("NettyServer.start error", e);
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}

View File

@@ -0,0 +1,32 @@
package com.jsowell.netty.server.yunkuaichong;
import com.jsowell.netty.decoder.StartAndLengthFieldFrameDecoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Resource
NettyServerHandler nettyServerHandler;
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// pipeline.addLast("decoder",new CustomDecoder());
pipeline.addLast("frameDecoder", new StartAndLengthFieldFrameDecoder(0x68));
pipeline.addLast("decoder", new ByteArrayDecoder());
pipeline.addLast("encoder", new ByteArrayDecoder());
//读超时时间设置为10s0表示不监控
pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("handler", nettyServerHandler);
}
}

View File

@@ -0,0 +1,203 @@
package com.jsowell.netty.server.yunkuaichong;
import com.google.common.collect.Lists;
import com.jsowell.common.enums.ykc.PileChannelEntity;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.service.yunkuaichong.YKCBusinessService;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.ReadTimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.xml.bind.DatatypeConverter;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* netty服务端处理类
*/
@ChannelHandler.Sharable
@Slf4j
@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Autowired
private YKCBusinessService ykcService;
/**
* 管理一个全局map保存连接进服务端的通道数量
*/
private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();
private final List<String> notPrintFrameTypeList = Lists.newArrayList("0x03");
/**
* 有客户端连接服务器会触发此函数
* 连接被建立并且准备进行通信时被调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
int clientPort = insocket.getPort();
//获取连接通道唯一标识
ChannelId channelId = ctx.channel().id();
//如果map中不包含此连接就保存连接
if (CHANNEL_MAP.containsKey(channelId)) {
log.info("客户端【{}】是连接状态,连接通道数量: {}", channelId, CHANNEL_MAP.size());
} else {
//保存连接
CHANNEL_MAP.put(channelId, ctx);
log.info("客户端【{}】, 连接netty服务器[IP:{}--->PORT:{}], 连接通道数量: {}", channelId, clientIp, clientPort, CHANNEL_MAP.size());
}
}
/**
* 有客户端发消息会触发此函数
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// log.info("加载客户端报文=== channelId:" + ctx.channel().id() + ", msg:" + msg);
// 下面可以解析数据保存数据生成返回报文将需要返回报文写入write函数
byte[] arr = (byte[]) msg;
// 获取帧类型
String frameType = YKCUtils.frameType2Str(BytesUtil.copyBytes(arr, 5, 1));
// 获取序列号域
int serialNumber = BytesUtil.bytesToIntLittle(BytesUtil.copyBytes(arr, 2, 2));
// new
String hexString = DatatypeConverter.printHexBinary(arr);
// 心跳包0x03日志太多造成日志文件过大改为不打印
if (!CollectionUtils.containsAny(notPrintFrameTypeList, frameType)) {
log.info("【<<<<<平台收到消息<<<<<】channel:{}, 帧类型:{}, 序列号域:{}, 报文:{}, new报文:{}, msg:{}",
ctx.channel().id(), frameType, serialNumber, BytesUtil.binary(arr, 16), hexString, Arrays.toString(arr));
}
// 处理数据
byte[] result = ykcService.process(arr, ctx.channel());
if (Objects.nonNull(result)) {
// 响应客户端
ByteBuf buffer = ctx.alloc().buffer().writeBytes(result);
this.channelWrite(ctx.channel().id(), buffer);
if (!CollectionUtils.containsAny(notPrintFrameTypeList, frameType)) {
log.info("【>>>>>平台响应消息>>>>>】:{}", BytesUtil.binary(result, 16));
}
}
}
/**
* 有客户端终止连接服务器会触发此函数
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ChannelId channelId = ctx.channel().id();
//包含此客户端才去删除
if (CHANNEL_MAP.containsKey(channelId)) {
ykcService.exit(channelId);
//删除连接
CHANNEL_MAP.remove(channelId);
log.info("客户端【{}】, 退出netty服务器[IP:{}--->PORT:{}], 连接通道数量: {}", channelId, clientIp, insocket.getPort(), CHANNEL_MAP.size());
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // (2)
// Channel incoming = ctx.channel();
// log.info("handlerAdded: handler被添加到channel的pipeline connect:" + incoming.remoteAddress());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // (3)
// Channel incoming = ctx.channel();
// log.info("handlerRemoved: handler从channel的pipeline中移除 connect:" + incoming.remoteAddress());
// ChannelMapByEntity.removeChannel(incoming);
// ChannelMap.removeChannel(incoming);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
// log.info("channel:【{}】读数据完成", channel.id());
super.channelReadComplete(ctx);
}
/**
* 服务端给客户端发送消息
*
* @param channelId 连接通道唯一id
* @param msg 需要发送的消息内容
*/
public void channelWrite(ChannelId channelId, Object msg) throws Exception {
ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);
if (ctx == null) {
log.info("通道【{}】不存在", channelId);
return;
}
if (msg == null || msg == "") {
log.info("服务端响应空的消息");
return;
}
//将客户端的信息直接返回写入ctx
ctx.write(msg);
//刷新缓存区
ctx.flush();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String socketString = ctx.channel().remoteAddress().toString();
ChannelId channelId = ctx.channel().id();
String pileSn = PileChannelEntity.getPileSnByChannelId(channelId.asLongText());
if (evt instanceof IdleStateEvent) { // 超时事件
IdleStateEvent event = (IdleStateEvent) evt;
boolean flag = false;
if (event.state() == IdleState.READER_IDLE) { // 读
flag = true;
log.info("Client-IP:【{}】, channelId:【{}】, pileSn:【{}】, READER_IDLE 读超时", socketString, channelId, pileSn);
} else if (event.state() == IdleState.WRITER_IDLE) { // 写
flag = true;
log.info("Client-IP:【{}】, channelId:【{}】, pileSn:【{}】, WRITER_IDLE 写超时", socketString, channelId, pileSn);
} else if (event.state() == IdleState.ALL_IDLE) { // 全部
flag = true;
log.info("Client-IP:【{}】, channelId:【{}】, pileSn:【{}】, ALL_IDLE 总超时", socketString, channelId, pileSn);
}
// if (flag) {
// ctx.channel().close();
// }
}
}
/**
* 发生异常会触发此函数
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ChannelId channelId = ctx.channel().id();
log.error("发生异常 channelId:{}", channelId.asShortText(), cause);
cause.printStackTrace();
// 如果桩连到平台在1分钟内没有发送数据过来会报ReadTimeoutException异常
if (cause instanceof ReadTimeoutException) {
if (log.isTraceEnabled()) {
log.trace("Connection timeout 【{}】", ctx.channel().remoteAddress());
}
log.info("【{}】发生了错误, 此连接被关闭, 此时连通数量: {}", channelId, CHANNEL_MAP.size());
ctx.channel().close();
}
}
}

View File

@@ -0,0 +1,25 @@
package com.jsowell.netty.service.yunkuaichong;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
/**
* 云快充处理service
*/
public interface YKCBusinessService {
/**
* 处理桩发来的请求
* 不需要应答的返回null
* @param msg 请求报文
* @param channel 通道信息
* @return 结果
*/
byte[] process(byte[] msg, Channel channel);
/**
* 桩退出
* @param channelId channelId
*/
void exit(ChannelId channelId);
}

View File

@@ -0,0 +1,64 @@
package com.jsowell.netty.service.yunkuaichong;
import com.jsowell.netty.command.ykc.GetRealTimeMonitorDataCommand;
import com.jsowell.netty.command.ykc.IssueQRCodeCommand;
import com.jsowell.netty.command.ykc.ProofreadTimeCommand;
import com.jsowell.netty.command.ykc.PublishPileBillingTemplateCommand;
import com.jsowell.netty.command.ykc.RebootCommand;
import com.jsowell.netty.command.ykc.StartChargingCommand;
import com.jsowell.netty.command.ykc.StopChargingCommand;
import com.jsowell.netty.command.ykc.UpdateFileCommand;
/**
* 云快充协议向充电桩发送命令service
*/
public interface YKCPushCommandService {
/**
* 发送启动充电指令
* @param startChargingCommand
*/
void pushStartChargingCommand(StartChargingCommand startChargingCommand);
/**
* 发送停止充电指令
* @param stopChargingCommand
*/
void pushStopChargingCommand(StopChargingCommand stopChargingCommand);
/**
* 读取实时监测数据命令
* @param command
*/
void pushGetRealTimeMonitorDataCommand(GetRealTimeMonitorDataCommand command);
/**
* 发送重启指令
* @param command
*/
void pushRebootCommand(RebootCommand command);
/**
* 发送下发二维码命令
* @param command
*/
void pushIssueQRCodeCommand(IssueQRCodeCommand command);
/**
* 发送对时命令
* @param command
*/
void pushProofreadTimeCommand(ProofreadTimeCommand command);
/**
* 下发计费模板命令
* @param command
*/
void pushPublishPileBillingTemplate(PublishPileBillingTemplateCommand command);
/**
* 发送远程更新命令
* @param command
*/
void pushUpdateFileCommand(UpdateFileCommand command);
}

View File

@@ -0,0 +1,100 @@
package com.jsowell.netty.service.yunkuaichong.impl;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.enums.ykc.OrderStatusEnum;
import com.jsowell.common.enums.ykc.PileChannelEntity;
import com.jsowell.common.enums.ykc.PileConnectorDataBaseStatusEnum;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.factory.YKCOperateFactory;
import com.jsowell.netty.handler.AbstractHandler;
import com.jsowell.netty.service.yunkuaichong.YKCBusinessService;
import com.jsowell.netty.service.yunkuaichong.YKCPushCommandService;
import com.jsowell.pile.domain.OrderBasicInfo;
import com.jsowell.pile.service.IOrderBasicInfoService;
import com.jsowell.pile.service.IPileConnectorInfoService;
import com.jsowell.pile.service.IPileMsgRecordService;
import com.jsowell.pile.vo.web.OrderListVO;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class YKCBusinessServiceImpl implements YKCBusinessService {
@Autowired
private IPileMsgRecordService pileMsgRecordService;
@Autowired
private IPileConnectorInfoService pileConnectorInfoService;
@Autowired
private IOrderBasicInfoService orderBasicInfoService;
@Autowired
private YKCPushCommandService ykcPushCommandService;
@Override
public byte[] process(byte[] msg, Channel channel) {
if (!YKCUtils.checkMsg(msg)) {
// 校验不通过,丢弃消息
return null;
}
YKCDataProtocol ykcDataProtocol = new YKCDataProtocol(msg);
// 获取帧类型
String frameType = YKCUtils.frameType2Str(ykcDataProtocol.getFrameType());
// 获取业务处理handler
AbstractHandler invokeStrategy = YKCOperateFactory.getInvokeStrategy(frameType);
return invokeStrategy.supplyProcess(ykcDataProtocol, channel);
}
@Override
public void exit(ChannelId channelId) {
// 获取桩编号
String pileSn = PileChannelEntity.getPileSnByChannelId(channelId.asLongText());
if (StringUtils.isBlank(pileSn)) {
return;
}
log.info("充电桩退出:{}, channelId:{}", pileSn, PileChannelEntity.getChannelByPileSn(pileSn).id());
// 充电桩断开连接,所有枪口都设置为【离线】
pileConnectorInfoService.updateConnectorStatusByPileSn(pileSn, PileConnectorDataBaseStatusEnum.OFF_NETWORK.getValue());
// 将此桩正在进行充电的订单状态改为 异常
List<OrderListVO> orderListVOS = orderBasicInfoService.selectChargingOrder(pileSn);
if (CollectionUtils.isNotEmpty(orderListVOS)) {
for (OrderListVO orderListVO : orderListVOS) {
if (StringUtils.equals(orderListVO.getOrderStatus(), OrderStatusEnum.IN_THE_CHARGING.getValue())) {
// 修改数据库订单状态
OrderBasicInfo info = OrderBasicInfo.builder()
.id(Long.parseLong(orderListVO.getId()))
.orderStatus(OrderStatusEnum.ABNORMAL.getValue())
.build();
orderBasicInfoService.updateOrderBasicInfo(info);
log.info("充电桩:{}退出, 修改充电桩正在充电的订单状态为异常, orderCode: {}", pileSn, orderListVO.getOrderCode());
}
}
}
// 记录充电桩退出msg
// 保存报文
String type = YKCFrameTypeCode.PILE_LOG_OUT.getCode() + "";
String jsonMsg = YKCFrameTypeCode.PILE_LOG_OUT.getValue();
pileMsgRecordService.save(pileSn, pileSn, type, jsonMsg, "");
// 自动重启 发送重启指令
// RebootCommand command = RebootCommand.builder().pileSn(pileSn).build();
// ykcPushCommandService.pushRebootCommand(command);
// 删除桩编号和channel的关系
PileChannelEntity.removeByPileSn(pileSn);
}
}

View File

@@ -0,0 +1,341 @@
package com.jsowell.netty.service.yunkuaichong.impl;
import com.google.common.collect.Lists;
import com.google.common.primitives.Bytes;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.enums.ykc.PileChannelEntity;
import com.jsowell.common.util.BytesUtil;
import com.jsowell.common.util.CRC16Util;
import com.jsowell.common.util.Cp56Time2a.Cp56Time2aUtil;
import com.jsowell.common.util.DateUtils;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.YKCUtils;
import com.jsowell.netty.command.ykc.GetRealTimeMonitorDataCommand;
import com.jsowell.netty.command.ykc.IssueQRCodeCommand;
import com.jsowell.netty.command.ykc.ProofreadTimeCommand;
import com.jsowell.netty.command.ykc.PublishPileBillingTemplateCommand;
import com.jsowell.netty.command.ykc.RebootCommand;
import com.jsowell.netty.command.ykc.StartChargingCommand;
import com.jsowell.netty.command.ykc.StopChargingCommand;
import com.jsowell.netty.command.ykc.UpdateFileCommand;
import com.jsowell.netty.service.yunkuaichong.YKCPushCommandService;
import com.jsowell.pile.service.IPileBasicInfoService;
import com.jsowell.pile.service.IPileBillingTemplateService;
import com.jsowell.pile.service.IPileConnectorInfoService;
import com.jsowell.pile.service.IPileModelInfoService;
import com.jsowell.pile.service.IPileMsgRecordService;
import com.jsowell.pile.vo.web.BillingTemplateVO;
import com.jsowell.pile.vo.web.PileModelInfoVO;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@Slf4j
@Service
public class YKCPushCommandServiceImpl implements YKCPushCommandService {
@Autowired
private IPileBillingTemplateService pileBillingTemplateService;
@Autowired
private IPileModelInfoService pileModelInfoService;
@Autowired
private IPileBasicInfoService pileBasicInfoService;
@Autowired
private RedisCache redisCache;
@Autowired
private IPileMsgRecordService pileMsgRecordService;
@Autowired
private IPileConnectorInfoService pileConnectorInfoService;
// 需要记录报文的数据帧类型
private final List<String> frameTypeList = Lists.newArrayList(
YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_RESTART_CODE.getBytes()),
YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_CONTROL_START_CODE.getBytes()),
YKCUtils.frameType2Str(YKCFrameTypeCode.REMOTE_STOP_CHARGING_CODE.getBytes())
);
public boolean push(byte[] msg, String pileSn, Enum<YKCFrameTypeCode> frameTypeCode) {
// 通过桩编号获取channel
Channel channel = PileChannelEntity.getChannelByPileSn(pileSn);
if (Objects.isNull(channel)) {
log.error("push命令失败, 桩号:{}无法获取到长连接, 请检查充电桩连接状态!", pileSn);
return false;
}
/**
* 拼接报文
*/
// 起始标志
byte[] head = new byte[]{0x68};
// 序列号域
byte[] serialNumber = new byte[]{0x00, 0x00};
// 加密标志
byte[] encryptFlag = new byte[]{0x00};
// 帧类型标志
byte[] frameType = new byte[]{(byte) ((YKCFrameTypeCode) frameTypeCode).getCode()};
// 序列号域+加密标志+帧类型标志+消息体
byte[] temp = Bytes.concat(serialNumber, encryptFlag, frameType, msg);
// 数据长度
byte[] length = BytesUtil.intToBytes(temp.length, 1);
// 帧校验域
byte[] crc = BytesUtil.intToBytes(CRC16Util.calcCrc16(temp));
// 返回报文
byte[] writeMsg = Bytes.concat(head, length, temp, crc);
// 返回完整的报文 string类型
String wholeMsg= BytesUtil.binary(writeMsg, 16);
log.info("[" + channel.remoteAddress() + "] 主动发送push请求信息:{}", wholeMsg);
ByteBuf byteBuf = channel.alloc().buffer().writeBytes(writeMsg);
ChannelFuture channelFuture = channel.writeAndFlush(byteBuf);
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
// 检查操作的状态
if (channelFuture.isSuccess()) {
log.info("push结果【成功】, remoteAddress:{}, channelId:{}, 报文:{}, ", channel.remoteAddress(), channel.id(), wholeMsg);
} else {
// 如果发生错误则访问描述原因的Throwable
Throwable cause = channelFuture.cause();
cause.printStackTrace();
log.info("push结果【失败】, remoteAddress:{}, channelId:{}, 报文:{}", channel.remoteAddress(), channel.id(), wholeMsg);
log.error("push发送命令失败", cause);
}
}
});
// 保存报文
String frameTypeStr = YKCUtils.frameType2Str(((YKCFrameTypeCode) frameTypeCode).getBytes());
if (frameTypeList.contains(frameTypeStr)) {
pileMsgRecordService.save(pileSn, null, frameTypeStr, null, wholeMsg);
}
return true;
}
/**
* 发送启动充电指令
*/
@Override
public void pushStartChargingCommand(StartChargingCommand command) {
String pileSn = command.getPileSn();
String connectorCode = command.getConnectorCode();
String orderCode = command.getOrderCode();
if (StringUtils.isEmpty(pileSn) || StringUtils.isEmpty(connectorCode) ) {
log.warn("远程启动充电, 充电桩编号和枪口号不能为空");
return;
}
if (StringUtils.isEmpty(orderCode)) {
log.warn("远程启动充电, 交易流水号不能为空");
return;
}
if (command.getChargeAmount() == null || BigDecimal.ZERO.equals(command.getChargeAmount())) {
log.warn("远程启动充电, 充电金额不能为0");
return;
}
// 枪口号
byte[] connectorCodeByteArr = BytesUtil.str2Bcd(connectorCode);
// 交易流水号
byte[] orderIdByteArr = BytesUtil.str2Bcd(orderCode);
// 桩编号
byte[] pileSnByteArr = BytesUtil.str2Bcd(pileSn);
// 逻辑卡号
String logicCardNum = StringUtils.isBlank(command.getLogicCardNum())
? Constants.ZERO
: command.getLogicCardNum();
byte[] logicCardNumByteArr = BytesUtil.checkLengthAndFrontAppendZero(BytesUtil.str2Bcd(logicCardNum), 16);
// 物理卡号
String physicsCardNum = StringUtils.isBlank(command.getPhysicsCardNum())
? Constants.ZERO
: command.getPhysicsCardNum();
byte[] physicsCardNumByteArr = BytesUtil.checkLengthAndFrontAppendZero(BytesUtil.str2Bcd(physicsCardNum), 16);
// 账户余额
BigDecimal chargeAmount = command.getChargeAmount();
byte[] accountBalanceByteArr = YKCUtils.getPriceByte(chargeAmount.toString(), 2);
byte[] msgBody = Bytes.concat(orderIdByteArr, pileSnByteArr, connectorCodeByteArr, logicCardNumByteArr, physicsCardNumByteArr, accountBalanceByteArr);
this.push(msgBody, pileSn, YKCFrameTypeCode.REMOTE_CONTROL_START_CODE);
log.info("【=====平台下发充电指令=====】:订单id:{}, 桩号:{}, 枪口号:{}, 逻辑卡号:{}, 物理卡号:{}, 账户余额:{}",
orderCode, pileSn, BytesUtil.bcd2Str(connectorCodeByteArr), logicCardNum, physicsCardNum, chargeAmount);
}
@Override
public void pushStopChargingCommand(StopChargingCommand command) {
String pileSn = command.getPileSn();
String connectorCode = command.getConnectorCode();
// 远程停机
byte[] msgBody = Bytes.concat(BytesUtil.str2Bcd(pileSn), BytesUtil.str2Bcd(connectorCode));
this.push(msgBody, pileSn, YKCFrameTypeCode.REMOTE_STOP_CHARGING_CODE);
log.info("【=====平台下发指令=====】:远程停止充电,桩号:{},枪口号:{}", pileSn, connectorCode);
}
@Override
public void pushGetRealTimeMonitorDataCommand(GetRealTimeMonitorDataCommand command) {
String pileSn = command.getPileSn();
String connectorCode = command.getConnectorCode();
byte[] msg = BytesUtil.str2Bcd(pileSn + connectorCode);
this.push(msg, pileSn, YKCFrameTypeCode.READ_REAL_TIME_MONITOR_DATA_CODE);
log.info("【=====平台下发指令=====】:获取充电桩:{} 的 {} 枪口实时数据信息", pileSn, connectorCode);
}
@Override
public void pushRebootCommand(RebootCommand command) {
String pileSn = command.getPileSn();
byte[] msg = BytesUtil.str2Bcd(pileSn + Constants.ZERO_ONE);
log.info("【=====平台下发指令=====】:重启充电桩:,{}", pileSn);
this.push(msg, pileSn, YKCFrameTypeCode.REMOTE_RESTART_CODE);
}
// 下发二维码
@Override
public void pushIssueQRCodeCommand(IssueQRCodeCommand command) {
log.info("异步下发二维码 thread:{}", Thread.currentThread().getName());
String pileSn = command.getPileSn();
// 桩编码
byte[] pileSnByteArr = BytesUtil.str2Bcd(pileSn);
// 二维码格式 0x00第一种 前缀+桩编号 0x01第二种 前缀+桩编号+枪编号
byte[] qrCodeTypeByteArr = Constants.oneByteArray;
// 二维码前缀 如“www.baidu.comNo=”
// String qrCodePrefix = "https://wx.charging.shbochong.cn/prepare_charge?code=";
// String qrCodePrefix = pileBasicInfoService.getPileQrCodeUrl(null);
String qrCodePrefix = pileConnectorInfoService.getPileConnectorQrCodeUrl(null);
byte[] qrCodePrefixByteArr = BytesUtil.str2Asc(qrCodePrefix);
// 二维码前缀长度 二维码前缀长度长度最大不超过200 字节
int length = qrCodePrefix.length();
byte[] qrCodePrefixLengthByteArr = BytesUtil.intToBytes(length, 1);
// 拼接消息体
byte[] msg = Bytes.concat(pileSnByteArr, qrCodeTypeByteArr, qrCodePrefixLengthByteArr, qrCodePrefixByteArr);
// push消息
boolean result = this.push(msg, pileSn, YKCFrameTypeCode.REMOTE_ISSUE_QRCODE_CODE);
log.info("=====平台下发指令===== :下发二维码,地址为:{}", qrCodePrefix);
}
/**
* 0x56 对时设置
* @param command
*/
@Override
public void pushProofreadTimeCommand(ProofreadTimeCommand command) {
log.info("充电桩对时thread:{}", Thread.currentThread().getName());
String pileSn = command.getPileSn();
// Date date = new Date();
// Date parseDate = DateUtils.parseDate("2023-02-28 16:45:20");
Date date = DateUtils.parseDate(DateUtils.getTime());
// 根据不同程序版本获取工具类
// String programVersion = redisCache.getCacheMapValue(CacheConstants.PILE_PROGRAM_VERSION, pileSn);
// AbsCp56Time2aUtil cp56Time2aUtil = Cp56Time2aFactory.getInvokeStrategy(programVersion);
// String encodeCP56Time2a = cp56Time2aUtil.date2HexStr(date);
byte[] bytes = Cp56Time2aUtil.date2Hbyte(date);
byte[] pileSnByteArr = BytesUtil.str2Bcd(pileSn);
byte[] msg = Bytes.concat(pileSnByteArr, bytes);
this.push(msg, pileSn, YKCFrameTypeCode.TIME_CHECK_SETTING_CODE);
log.info("[充电桩:{}对时, 时间:{}, CP56Time2a:{}]", pileSn, DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date), BytesUtil.binary(bytes, 16));
}
@Override
public void pushPublishPileBillingTemplate(PublishPileBillingTemplateCommand command) {
BillingTemplateVO billingTemplateVO = command.getBillingTemplateVO();
String pileSn = command.getPileSn();
// 转换
byte[] messageBody = pileBillingTemplateService.generateBillingTemplateMsgBody(pileSn, billingTemplateVO);
// 发送
if (messageBody != null) {
this.push(messageBody, pileSn, YKCFrameTypeCode.BILLING_TEMPLATE_SETTING_CODE);
}
}
@Override
public void pushUpdateFileCommand(UpdateFileCommand command) {
List<String> pileSns = command.getPileSnList();
if (CollectionUtils.isEmpty(pileSns)) {
return;
}
List<PileModelInfoVO> list = pileModelInfoService.getPileModelInfoByPileSnList(pileSns);
if (CollectionUtils.isEmpty(list)) {
return;
}
// 获取桩型号 01直流 02交流
byte[] pileModelType;
for (PileModelInfoVO pileModelInfoVO : list) {
byte[] pileSnByteArr = BytesUtil.str2Bcd(pileModelInfoVO.getPileSn());
// 数据库 1- 快充(直流) 2-慢充(交流)
/*if (StringUtils.equals(pileModelInfoVO.getSpeedType(), Constants.ONE)) {
pileModelType = Constants.oneByteArray;
} else {
pileModelType = Constants.twoByteArray;
}*/
pileModelType = Constants.zeroByteArray;
// 额定功率
String ratedPower = pileModelInfoVO.getRatedPower();
int i = Integer.parseInt(ratedPower);
// byte[] ratedPowerByteArr = Base64.getDecoder().decode(ratedPower);
byte[] ratedPowerByteArr = BytesUtil.checkLengthAndBehindAppendZero(Constants.zeroByteArray, 4);
// 升级服务器地址
byte[] updateServerAddressByteArr = BytesUtil.checkLengthAndBehindAppendZero(BytesUtil.str2Asc(Constants.updateServerIP), 32);
// 升级服务器端口
byte[] updateServerPortByteArr = BytesUtil.checkLengthAndBehindAppendZero(Constants.updateServerPort, 4);
// byte[] updateServerPortByteArr = BytesUtil.checkLengthAndBehindAppendZero(BytesUtil.str2Bcd("15"), 4);
// 用户名
byte[] userNameByteArr = BytesUtil.checkLengthAndBehindAppendZero(BytesUtil.str2Asc(Constants.updateServerUserName), 32);
// 密码
byte[] passwordByteArr = BytesUtil.checkLengthAndBehindAppendZero(BytesUtil.str2Asc(Constants.updateServerPassword), 32);
// 文件路径
byte[] filePathByteArr = BytesUtil.checkLengthAndBehindAppendZero(BytesUtil.str2Asc(Constants.filePath), 64);
// 执行控制 01立即执行 02空闲执行
byte[] performTypeByteArr = Constants.oneByteArray;
// 下载超时时间 单位min
byte[] overTimeByteArr = new byte[]{0x05};
byte[] msgBody = Bytes.concat(pileSnByteArr, pileModelType, ratedPowerByteArr, updateServerAddressByteArr,
updateServerPortByteArr, userNameByteArr, passwordByteArr, filePathByteArr, performTypeByteArr, overTimeByteArr);
this.push(msgBody, pileModelInfoVO.getPileSn(), YKCFrameTypeCode.REMOTE_UPDATE_CODE);
log.info("【=====平台下发指令=====】:远程更新, 桩号:{}, 类型:{}, 额定功率:{}, 服务器地址:{}, 端口号:{}, 用户名:{}, 密码:{}, 文件路径:{}",
pileModelInfoVO.getPileSn(), pileModelType, BytesUtil.bcd2Str(ratedPowerByteArr), BytesUtil.binary(updateServerAddressByteArr, 16),
BytesUtil.binary(updateServerPortByteArr, 16), BytesUtil.binary(userNameByteArr, 16), BytesUtil.binary(passwordByteArr, 16),
BytesUtil.binary(filePathByteArr, 16));
}
}
}