Files
jsowell-charger-web/jsowell-common/src/main/java/com/jsowell/common/util/YKCUtils.java

384 lines
13 KiB
Java
Raw Normal View History

2023-03-04 16:29:55 +08:00
package com.jsowell.common.util;
2024-09-03 10:04:33 +08:00
import com.google.common.collect.ImmutableMap;
2023-03-04 16:29:55 +08:00
import com.google.common.primitives.Bytes;
2024-11-01 15:21:28 +08:00
import com.jsowell.common.constant.CacheConstants;
2024-02-20 16:05:54 +08:00
import com.jsowell.common.constant.Constants;
2024-11-01 15:21:28 +08:00
import com.jsowell.common.core.domain.ykc.YKCBaseMessage;
import com.jsowell.common.core.domain.ykc.YKCDataProtocol;
2023-12-26 14:41:25 +08:00
import com.jsowell.common.core.domain.ykc.YKCFrameTypeCode;
2024-11-19 14:55:31 +08:00
import com.jsowell.common.core.redis.RedisCache;
2024-11-01 15:21:28 +08:00
import com.jsowell.common.core.redis.StaticRedisCache;
import com.jsowell.common.enums.ykc.PileChannelEntity;
2024-09-03 09:36:27 +08:00
import com.jsowell.common.enums.ykc.ReturnCodeEnum;
import com.jsowell.common.exception.BusinessException;
2024-11-01 15:21:28 +08:00
import io.netty.channel.ChannelHandlerContext;
2023-03-04 16:29:55 +08:00
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
2024-09-03 09:36:27 +08:00
import java.util.Map;
2024-11-19 14:55:31 +08:00
import java.util.concurrent.TimeUnit;
2023-03-04 16:29:55 +08:00
@Slf4j
public class YKCUtils {
private static final BigDecimal multiple = new BigDecimal("100000");
/**
* 校验云快充请求数据格式
*
* @param msg 请求报文
* @return
*/
public static boolean checkMsg(byte[] msg) {
2024-07-31 15:27:59 +08:00
// log.info("checkMsg:{}", BytesUtil.binary(msg, 16));
2023-03-04 16:29:55 +08:00
// 起始标志
byte[] head = BytesUtil.copyBytes(msg, 0, 1);
// 数据长度
byte[] length = BytesUtil.copyBytes(msg, 1, 1);
// 序列号域
byte[] serialNumber = BytesUtil.copyBytes(msg, 2, 2);
// 加密标志
byte[] encryptFlag = BytesUtil.copyBytes(msg, 4, 1);
// 帧类型标志
byte[] frameType = BytesUtil.copyBytes(msg, 5, 1);
// 消息体
byte[] msgBody = BytesUtil.copyBytes(msg, 6, msg.length - 8);
// 帧校验域
byte[] crcByte = new byte[]{msg[msg.length - 2], msg[msg.length - 1]};
//起始位必须是0x68
if (0x68 != head[0]) {
log.error("起始位必须是0x68");
return false;
}
// 序列号域+加密标志+帧类型标志+消息体
byte[] data = Bytes.concat(serialNumber, encryptFlag, frameType, msgBody);
// 校验长度
if (data.length != BytesUtil.bytesToIntLittle(length)) {
2024-07-31 15:12:05 +08:00
log.error("数据长度不正确, 数据长度:{}, 实际长度:{}", BytesUtil.bytesToIntLittle(length), data.length);
2023-03-04 16:29:55 +08:00
return false;
}
// CRC校验 source target
String sourceCRC = String.format("%04x", BytesUtil.bytesToInt(crcByte, 0));
String targetCRC = String.format("%04x", CRC16Util.calcCrc16(data));
String oldTargetCRC = String.format("%04x", CRC16Util.calcCrc16Old(data));
2023-06-29 09:16:50 +08:00
// 将高低位位置反转得出新的crc
String lowString = StringUtils.substring(targetCRC, 0, 2);
String highString = StringUtils.substring(targetCRC, 2, 4);
String crc = highString + lowString;
// 若目标crc和高低位反转前/后的crc都不一致则校验不通过
if (sourceCRC.equalsIgnoreCase(targetCRC) || sourceCRC.equalsIgnoreCase(crc)) {
return true;
2023-03-04 16:29:55 +08:00
}
2024-11-01 15:21:28 +08:00
log.error("CRC校验不通过, 源crc:{}, 计算得出crc:{}, 老crc计算:{}, 高低位反转后crc:{}, 帧类型:{}, 帧名称:{}, 报文:{}"
2023-12-26 14:41:25 +08:00
, sourceCRC, targetCRC, oldTargetCRC, crc, YKCUtils.frameType2Str(frameType),
2024-11-01 15:21:28 +08:00
YKCFrameTypeCode.getFrameTypeStr(YKCUtils.frameType2Str(frameType)), BytesUtil.binary(msg, 16));
2023-06-29 09:16:50 +08:00
return false;
2023-03-04 16:29:55 +08:00
}
2024-11-01 15:21:28 +08:00
/**
* 获取结果报文
* @param ykcDataProtocol
* @param messageBody
* @return
*/
public static 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.PlatformAnswersRelation.getResponseFrameTypeBytes(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));
}
public static byte[] getResult(YKCBaseMessage message, byte[] messageBody) {
// 起始标志
String header = message.getHeader();
byte[] headBytes = BytesUtil.stringToHexBytes(header, 1);
// 序列号域
int serialNumber = message.getSerialNumber();
byte[] serialNumberBytes = BytesUtil.intToBytesLittle(serialNumber, 2);
// 加密标志
int encryptFlag = message.getEncryptFlag();
byte[] encryptFlagBytes = BytesUtil.intToBytesLittle(encryptFlag, 1);
// 请求帧类型
String frameType = message.getFrameType();
byte[] requestFrameTypeBytes = BytesUtil.hexString2Bytes(frameType.replace("0x", ""));
// 应答帧类型
byte[] responseFrameTypeBytes = YKCFrameTypeCode.PlatformAnswersRelation.getResponseFrameTypeBytes(requestFrameTypeBytes);
// 数据域 值为“序列号域+加密标志+帧类型标志+消息体”字节数之和
byte[] dataFields = Bytes.concat(serialNumberBytes, encryptFlagBytes, responseFrameTypeBytes, messageBody);
// 计算crc 从序列号域到数据域的 CRC 校验
int crc16 = CRC16Util.calcCrc16(dataFields);
return Bytes.concat(headBytes, BytesUtil.intToBytes(dataFields.length, 1), dataFields, BytesUtil.intToBytes(crc16));
}
/**
* 保存桩最后链接到平台的时间
* @param pileSn 桩编号
*/
public static void saveLastTimeAndCheckChannel(String pileSn, ChannelHandlerContext ctx) {
String redisKey = CacheConstants.PILE_LAST_CONNECTION + pileSn;
StaticRedisCache.staticRedisCache.setCacheObject(redisKey, DateUtils.getDateTime(), CacheConstants.cache_expire_time_30d);
// 保存桩号和channel的关系
PileChannelEntity.checkChannel(pileSn, ctx);
}
/**
* 阻止重复帧, 并返回是否重复
* 判断逻辑相同的channelId和序列号的请求30秒内只允许一次
* @return true 重复
*/
public static boolean verifyTheDuplicateRequest(YKCDataProtocol ykcDataProtocol, ChannelHandlerContext ctx) {
// 获取序列号域
int serialNumber = BytesUtil.bytesToIntLittle(ykcDataProtocol.getSerialNumber());
// 获取channelId
String channelId = ctx.channel().id().asShortText();
String redisKey = "Request_" + channelId + "_" + serialNumber;
Boolean result = StaticRedisCache.staticRedisCache.setnx(redisKey, ykcDataProtocol.getHEXString(), 30);
// result返回false说明没有设置成功就是说已经有相同请求了所以返回true重复
return !result;
}
/**
* 从消息中获取pileSn
*/
public static byte[] getPileSnBytes(String pileSn) {
// 通过pileSN转换为byte[]
return BytesUtil.hexStringToByteArray(pileSn);
}
2023-03-04 16:29:55 +08:00
/**
* 转换电压电流以及起始soc
* 精确到小数点后一位待机置零
*
* @param bytes
* @return
*/
public static String convertVoltageCurrent(byte[] bytes) {
// 转换为int 最后一位是小数位
int i = BytesUtil.bytesToIntLittle(bytes);
// 使用BigDecimal
BigDecimal bigDecimal = new BigDecimal(i);
BigDecimal divide = bigDecimal.divide(new BigDecimal(10), 1, BigDecimal.ROUND_UP);
return divide.toString();
}
/**
* 转换小数点
*
* @param bytes
* @param scale 小数位
* @return
*/
public static String convertDecimalPoint(byte[] bytes, int scale) {
2024-11-01 15:21:28 +08:00
// 转换为int
2023-03-04 16:29:55 +08:00
int i = BytesUtil.bytesToIntLittle(bytes);
// 使用BigDecimal
BigDecimal bigDecimal = new BigDecimal(i);
BigDecimal divide = bigDecimal.divide(BigDecimal.valueOf(Math.pow(10d, scale)), scale, BigDecimal.ROUND_UP);
return divide.toString();
}
/**
* 获取价格byte数组
* 价格转换为byte数组
* @param price 实际价格单位元
* @param scale 保留小数位
* @return
*/
public static byte[] getPriceByte(String price, int scale) {
// 保留小数位 例:保留5位小数需要乘以10的5次方
BigDecimal value = BigDecimal.valueOf(Math.pow(10d, scale));
int i = new BigDecimal(price).multiply(value).intValue();
return BytesUtil.intToBytesLittle(i, 4);
}
/**
2024-02-20 16:05:54 +08:00
* byte转帧类型字符串 "01"--> "0x01"
2023-03-04 16:29:55 +08:00
* @param bytes
* @return
*/
public static String frameType2Str(byte[] bytes) {
String s = BytesUtil.bin2HexStr(bytes);
2024-09-14 16:07:59 +08:00
return frameType2Str(s);
}
/**
* "01"--> "0x01"
* @param frameType 帧类型
* @return
*/
public static String frameType2Str(String frameType) {
return Constants.HEX_PREFIX + frameType;
2024-02-20 16:05:54 +08:00
}
public static byte[] frameTypeStr2Bytes(String frameTypeStr) {
byte[] bytes = BytesUtil.hexString2Bytes(frameTypeStr);
return bytes;
2023-03-04 16:29:55 +08:00
}
2023-03-27 11:49:32 +08:00
/**
* 转换温度
* BIN 1 整形偏移量-50待机置零
*/
public static String transitionTemperature(byte[] bytes) {
String s = BytesUtil.binary(bytes, 10);
int i = Integer.parseInt(s);
2023-03-27 17:03:28 +08:00
if (i > 0) {
return String.valueOf(i - 50);
}
return "";
2023-03-04 16:29:55 +08:00
}
2024-08-02 15:45:15 +08:00
/**
* 解析vin
*/
public static String parseVin(byte[] vinCodeByteArr) {
return BytesUtil.ascii2Str(vinCodeByteArr);
}
2024-09-03 09:36:27 +08:00
2024-09-03 10:04:33 +08:00
/**
* 解析充电桩连接器编号
*
* @param pileConnectorCode 充电桩连接器编号
* @param totalLength 总长度
* @param connectorOffset 连接器编号的偏移量
* @return 包含桩编号和连接器编号的映射
*/
private static Map<String, String> parsePileConnector(String pileConnectorCode, int totalLength, int connectorOffset) {
String pileSn = StringUtils.substring(pileConnectorCode, 0, totalLength - 2);
String connectorCode = StringUtils.substring(pileConnectorCode, connectorOffset);
return ImmutableMap.of("pileSn", pileSn, "connectorCode", connectorCode);
}
2024-09-03 09:36:27 +08:00
/**
* 解析充电桩编号
2024-09-03 10:04:33 +08:00
*
* @param pileConnectorCode 充电桩连接器编号
* @return 包含桩编号和连接器编号的映射
2024-09-03 09:36:27 +08:00
*/
public static Map<String, String> parsePileConnectorCode(String pileConnectorCode) {
2024-09-03 10:04:33 +08:00
int length = pileConnectorCode.length();
2024-09-03 10:15:10 +08:00
// 如果length不等于14, 16, 8, 10, 2, 判断为错误的长度
if (length != Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EV
&& length != Constants.PILE_SN_LENGTH_FOR_EV
&& length != Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EBIKE
&& length != Constants.PILE_SN_LENGTH_FOR_EBIKE
&& length != 2) {
throw new BusinessException(ReturnCodeEnum.CODE_DATA_LENGTH_ERROR.getValue(), "Invalid pile connector code length: " + length);
}
if (length == Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EV) {
2024-09-03 10:04:33 +08:00
// 汽车桩
2024-09-03 10:15:10 +08:00
return parsePileConnector(pileConnectorCode, Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EV, pileConnectorCode.length() - 2);
} else if (length == Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EBIKE) {
// 电单车
return parsePileConnector(pileConnectorCode, Constants.PILE_CONNECTOR_CODE_LENGTH_FOR_EBIKE, pileConnectorCode.length() - 2);
} else if (length == Constants.PILE_SN_LENGTH_FOR_EV || length == Constants.PILE_SN_LENGTH_FOR_EBIKE) {
// 充电桩编号
return ImmutableMap.of("pileSn", pileConnectorCode, "connectorCode", "");
2024-09-03 09:36:27 +08:00
} else {
2024-09-03 10:15:10 +08:00
// 枪口号
return ImmutableMap.of("pileSn", "", "connectorCode", pileConnectorCode);
2024-09-03 09:36:27 +08:00
}
}
/**
* 获取pileSn
* @param pileConnectorCode 充电桩枪口编号
* @return
*/
public static String getPileSn(String pileConnectorCode) {
Map<String, String> map = parsePileConnectorCode(pileConnectorCode);
return map.get("pileSn");
}
/**
* 获取枪口号
* @param pileConnectorCode 充电桩枪口编号
* @return
*/
public static String getConnectorCode(String pileConnectorCode) {
Map<String, String> map = parsePileConnectorCode(pileConnectorCode);
return map.get("connectorCode");
}
2024-11-19 14:55:31 +08:00
/**
* 保存soc
* @param transactionCode
* @param soc
*/
public static void saveSOC(String transactionCode, String soc) {
2024-11-19 15:41:32 +08:00
if (StringUtils.isBlank(transactionCode) || Double.parseDouble(soc) <= 0) {
return;
}
2024-11-19 14:55:31 +08:00
String hashKey = CacheConstants.GET_THE_SOC + transactionCode;
RedisCache staticRedisCache = StaticRedisCache.staticRedisCache;
try {
// 检查值是否符合要求
if (!soc.matches("^-?\\d+(\\.\\d{1})?$")) {
throw new IllegalArgumentException("输入的值必须是一个最多有一位小数的数字: " + soc);
}
double doubleValue = Double.parseDouble(soc);
// 获取当前的最小值和最大值
String currentMin = (String) staticRedisCache.hget(hashKey, "min");
String currentMax = (String) staticRedisCache.hget(hashKey, "max");
double min = currentMin != null ? Double.parseDouble(currentMin) : Double.MAX_VALUE;
double max = currentMax != null ? Double.parseDouble(currentMax) : Double.MIN_VALUE;
// 更新最小值或最大值
if (doubleValue < min) {
staticRedisCache.hset(hashKey, "min", soc, 7, TimeUnit.DAYS);
}
if (doubleValue > max) {
staticRedisCache.hset(hashKey, "max", soc, 7, TimeUnit.DAYS);
}
} catch (Exception e) {
e.printStackTrace();
}
}
2024-11-19 16:20:10 +08:00
/**
* 根据的交易码获取当前soc
* @param transactionCode
*/
public static String getCurrentSOC(String transactionCode) {
String hashKey = CacheConstants.GET_THE_SOC + transactionCode;
RedisCache staticRedisCache = StaticRedisCache.staticRedisCache;
Map<String, Object> cacheMap = staticRedisCache.getCacheMap(hashKey);
// 获取最小值和最大值, 两个值中最大的为当前soc
return (String) cacheMap.get("max");
}
2024-11-19 14:55:31 +08:00
2023-03-04 16:29:55 +08:00
}