# charing **Repository Path**: smartom/charing ## Basic Information - **Project Name**: charing - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-11-13 - **Last Updated**: 2024-11-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 充电桩系统TCP/UDP系统对接demo # 源码:(十路和云快充以及后面对接UDP产品) # https://gitee.com/smartom/charing 说明:1.因为demo示例可能进行迭代更新已适配不同设备型号,本示例代码已传至码云。 ```bash 2. 本示例仅列举目前仅列举充电桩1015,10口设备,以及云快充协议。 2. 源码地址:https://gitee.com/smartom/charing ``` ## 1.快速开始 环境需求: ```bash jdk 1.8+ redis ``` ### 1.1 云快充对接示例: 1.首先更新汽车桩设备链接地址:云快充设备链接地址:xxxxxx:18082 2.运行demo。 3.url请求示例: 设备编号14位,设备编号不够后补0。 充电枪从01开始计算 3.1 下发计费模型请求: method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从01表示1号插口02表示2号插口 ``` http://127.0.0.1:10086/car/testIssueModel/?pileNo=11133523880000&gunNo=01 ``` 3.2 下发通电请求: method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从01表示1号插口02表示2号插口 ``` http://127.0.0.1:10086/car/testCharge/?pileNo=11133523880000&gunNo=01 ``` 3.3停止充电请求: method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从01表示1号插口02表示2号插口 ``` http://127.0.0.1:10086/car/testStopCharge/?pileNo=11133523880000&gunNo=01 ``` ### 1.2 1082 十口产品对接示例: 1.首先更新十路充电桩设备链接地址:云快充设备链接地址:xxxxxx,18080 2.运行demo。 3.url请求示例: 设备编号8位。 充电枪从0开始计算 3.1 下发充电: method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从0表示1号插口 ,isOpen=1表示打开充电 ``` http://127.0.0.1:10086/charge-test/testJsy2charge/?pileNo=11000833&gunNo=0&isOpen=1 ``` 3.2 下发停止充电 method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从0表示1号插口 ,isOpen=0表示停止充电 ``` http://127.0.0.1:10086/charge-test/testJsy2charge/?pileNo=11000833&gunNo=0&isOpen=0 ``` 3.3 更新充电时长 method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从0表示1号插口 ``` http://127.0.0.1:10086/charge-test/testJsy2Time/?pileNo=11000833&gunNo=0 ``` 3.4 清空数据 method:GET 参数说明:pileNo:设备号, gunNo:充电枪插口从0表示1号插口 ``` http://127.0.0.1:10086/charge-test/testJsy2Bomb/?pileNo=11000833 ``` ## 2. 项目目录结构说明 ```bash ├── BootNettyUdpSimpleChannelInboundHandler#channelRead0 #处理udp数据 ├── NettyChannelInitializer.java #初始化通道,主要用于网络I/O事件,记录日志,编码、解码消息 ├── NettyDecoder.java #Netty解码器 ├── NettyHandler.java #已废止 ├── NettyIdleStateHandler.java #心跳机制,检查远端是否存活。 ├── NettyInitializer.java #自定义初始化器 ├── NettyProperties.java # nacos参数接受 ├── NettyServer.java #配置netty,启动netty ├── NettyStartListener.java #springboot启动后加载netty启动 ├── NettyYDDecoder.java #已废止 ├── TcpInboundAdapter.java #读取客户端数据 └── WebSocketServer.java #springboot 处理websocket 充电桩项目未用到。 ``` ## 3.netty加载顺序 ![](https://typora-1253396867.cos.ap-guangzhou.myqcloud.com/image-20241112193324008.png) ## 4.业务逻辑实现 ![image-20241113215413461](https://typora-1253396867.cos.ap-guangzhou.myqcloud.com/image-20241113215413461.png) 1.新建`TcpInboundAdapter` 重写``ChannelInboundHandlerAdapter#``用于接受用户信息 ```java /** 以下表示有一、二、三步骤给出具体实现 **/ @Slf4j public class TcpInboundAdapter extends ChannelInboundHandlerAdapter { public static final ChannelGroup clients; //管理多个 Channel对象的集合 public static final YxCardContextService yxCardContextService; //一、具体策略类 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { InetSocketAddress localSocket = (InetSocketAddress) ctx.channel().localAddress(); int port = localSocket.getPort(); //获取端口 Channel client = ctx.channel(); if (BizConstant.HardSoftFactoryPort.YU_XIN == port) { YXBasicDto dto = YxObjectUtil.parse(payload); //二、将接受到的信息转包装成YxBasicDto类(下面给出) if (dto.getFrameType().equals("03")) { //云快充心跳 String deviceNo = dto.getData().substring(0, 14); //截取设备号 if (getChannelId(deviceNo, ctx.channel().id().asShortText()) == null) { updateCacheByDeviceNo(deviceNo, client); //三、判断是否需要将channel添加到ChannelGroup和redis中 } } yxCardContextService.execute(dto, client); //执行云快充策略方法在“一”中执行 } } } ``` ### 一、 云快充具体策略类 ```java @Service @Slf4j public class YxCardContextService { @Autowired //存储接受到的云快充指令执行对应的方法 private final Map strategyMap = new ConcurrentHashMap<>(); public YxCardContextService(Map strategyMap) { this.strategyMap.clear(); strategyMap.forEach((k, v) -> this.strategyMap.put(k, v)); //将策略添加 } public void execute(YXBasicDto dto, Channel client) throws Exception { String className = YXEventTagEnum.getValueByKey(dto.getFrameType()); if (StringUtils.isNotBlank(className)) { strategyMap.get(className).doExecute(dto, client); //执行策略方法(示例) } } } ``` #### 1.登录 ```java //具体策略角色 public interface IYXContextListener { //策略模式运算 void doExecute(YXBasicDto dto, Channel client) throws Exception; } ``` ```java //具体策略角色 //充电桩每次复位或通信 离线,都需重新登录,并 成功后才能进行后续交互【①设备登录以及应答】 @Service(value = YxConstant.Service.CMD_1) @AllArgsConstructor @Slf4j public class YxLoginListener implements IYXContextListener { /** * 1 桩编码 BCD 码 7 不足 7 位补 0 * 2 桩类型 BIN 码 1 0 表示直流桩,1 表示交流桩 * 3 充电枪数量 BIN 码 1 * 4 通信协议版本 BIN 码 1 版本号乘 10,v1.0 表示 0x0A * 5 程序版本 ASCII 码 8 不足 8 位补零 * 6 网络链接类型 BIN 码 1 0x00 SIM 卡 * 0x01 LAN * 0x02 WAN * 0x03 其他 * 7 Sim 卡 BCD 码 10 不足 10 位补零,取不到置零 * 8 运营商 BIN 码 1 0x00 移动 * 0x02 电信 * 0x03 联通 * 0x04 其他 */ private static final int[] number = {14, 2, 2, 2, 16, 2, 20, 2}; private IRechargePileService iRechargePileService; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { log.info("接收JMD-YX登录认证(CMD = 01):" + dto); String pileNo = StringUtils.substring(dto.getData(), 0, number[0]); String realPileNo = StringUtils.substring(dto.getData(), 0, number[0]); int pileNum = iRechargePileService.count(Wrappers.query().lambda().eq(RechargePile::getPileNo, realPileNo).eq(RechargePile::getIsDeleted, UnicornConstant.DB_NOT_DELETED)); /** * 68(起始标志)0C(数据长度)0000(序列号域)00(加密标志)02(类型)55 03 14 12 78 23 * 05(桩编码)00(登陆结果:成功)DA 4C(帧校验域) */ StringBuilder builder = new StringBuilder(pileNo); builder.append(pileNum > 0 ? "00" : "01"); String replyMsg = YxObjectUtil.replyMessage(builder.toString(), dto.getSequence(), YxConstant.Cmd.CMD_2); log.info("JMD-YX【" + pileNo + "】登录认证应答(CMD = 02):" + replyMsg); ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); ///登录后下发对时 if (pileNum > 0) { StringBuilder builder1 = new StringBuilder(); builder1.append(StringUtils.leftPad(pileNo, 14, "0")); Date time = DateUtil.now(); //②返回验证结果 builder1.append(YxObjectUtil.getTime2CP56time2a(time)); String timeReplyMsg = YxObjectUtil.pushMessage(builder1.toString(), YxConstant.Cmd.CMD_56); log.info("JMD-YX【" + pileNo + "下发时间" + DateUtil.formatDateTime(time) + "】对时下发(CMD = 56):" + timeReplyMsg); ByteBuf buf1 = Unpooled.buffer(timeReplyMsg.getBytes().length); buf1.writeBytes(CommonUtil.hexToByteArray(timeReplyMsg)); client.writeAndFlush(buf1); } } } ``` #### 2. 心跳上报以及应答 ```java @Service(value = YxConstant.Service.CMD_3) @AllArgsConstructor @Slf4j public class YxHeartbeatListener implements IYXContextListener { private static final int[] number = {14, 2, 2, 2, 16, 2, 20, 2}; private IRechargePileService iRechargePileService; private IRechargeGunService iRechargeGunService; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { log.info("JMD-YX型号接收心跳包(CMD = 03):" + dto); String pileNo = StringUtils.substring(dto.getData(), 0, number[0]); String gunNo = StringUtils.substring(dto.getData(), number[0], number[0] + number[1]); //00-正常 01-故障 String gunStatus = StringUtils.substring(dto.getData(), dto.getData().length() - 2); RechargeGun rechargeGun = iRechargeGunService.getOne(Wrappers.query().lambda() .eq(RechargeGun::getPileNo, pileNo) .eq(RechargeGun::getGunNo, gunNo)); /** * 68(起始标志)0C(数据长度)0000(序列号域)00(加密标志)02(类型)55 03 14 12 78 23 * 05(桩编码)00(登陆结果:成功)DA 4C(帧校验域) */ StringBuilder builder = new StringBuilder(); builder.append(pileNo).append(gunNo); if (rechargeGun != null) { builder.append("00"); //心跳包应答 String replyMsg = YxObjectUtil.replyMessage(builder.toString(), dto.getSequence(), YxConstant.Cmd.CMD_4); log.info("YX型号设备号:【"+pileNo+"】心跳包应答(CMD = 04)" + replyMsg); ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); //根据心跳包设置充电桩状态 rechargeGun.setOnlineStatus(BizConstant.OnOffLine.ONLINE); if (gunStatus.equals("01")) { rechargeGun.setWorkStatus(BizConstant.WorkStatus.FAULT); } //更新充电桩状态 iRechargeGunService.updateById(rechargeGun); } } } ``` #### 3.1.下发计费模型(cmd=58) 运营平台->充电桩 ```java String payload = yxIssueModel(chargeRules, timeModelList, gun, gunmodel); //根据尖、峰、平、谷拼接 if (Func.isBlank(payload)) { continue; } StringBuilder hexstring = new StringBuilder(); String deviceNo = gun.getPileNo(); //桩编码 hexstring.append(StringUtil.leftPad(deviceNo, 14, '0')); //计费模型编号 hexstring.append(gunmodel.getModelCode()); hexstring.append(payload); Map channels = (Map) redisUtil.get(CacheNames.TCP_CHANNEL); if (channels == null || channels.get(deviceNo) == null) { gun.setModelId(gunmodel.getId()); continue; } Channel channel = TcpInboundAdapter.clients.find(channels.get(deviceNo)); String sendMessage = YxObjectUtil.pushMessage(hexstring.toString(), YxConstant.Cmd.CMD_58); log.info("发送JMD-YX报文[" + deviceNo + "," + gun.getGunNo() + "](CMD=58): "); log.info(sendMessage); if(channel==null){ JSONObject json = new JSONObject(); json.put("pileNo",gun.getPileNo()); json.put("body",sendMessage); rocketMQTemplate.asyncSend(mqProperties.getSocketTopic()+":YX", json.toJSONString(), RocketmqSendCallbackBuilder.commonCallback()); return true; } ByteBuf buf = Unpooled.buffer(sendMessage.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(sendMessage)); ``` #### 3.2计费模型验证请求 充电桩->运营平台 ```java @Service(value = YxConstant.Service.CMD_5) @AllArgsConstructor @Slf4j public class YxCalculateModeVerfyListener implements IYXContextListener { private static final int[] number = {14, 4}; private IRechargePileService iRechargePileService; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { String pileNo = StringUtils.substring(dto.getData(), 0, number[0]); String modeNo = StringUtils.substring(dto.getData(), number[0], number[0] + number[1]); StringBuilder builder = new StringBuilder(); builder.append(pileNo); builder.append(modeNo); builder.append("01"); String replyMsg = YxObjectUtil.replyMessage(builder.toString(), dto.getSequence(), YxConstant.Cmd.CMD_6); log.info("JDM-YX计费模型验证请求应答(CMD = 06):" + replyMsg); ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); } } ``` #### 3.3充电桩计费模型请求 充电桩->运营平台 ```java @Service(value = YxConstant.Service.CMD_9) @AllArgsConstructor @Slf4j public class YxCalculateModeListener implements IYXContextListener { private static final int[] number = {14, 4}; private IChargeRuleService iChargeRuleService; private IGunModelService iGunModelService; private ITimeModelService iTimeModelService; private IRechargeGunService iRechargeGunService; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { String pileNo = StringUtils.substring(dto.getData(), 0, number[0]); StringBuilder builder = new StringBuilder(); RechargeGun gun = iRechargeGunService.getOne(Wrappers.query().lambda().eq(RechargeGun::getPileNo, pileNo).eq(RechargeGun::getGunNo, "01")); if (gun != null) { builder.append(StringUtils.rightPad(pileNo, 14, "0")); builder.append("0100"); GunModel gunmodel = iGunModelService.getById(gun.getModelId()); if(null == gunmodel){ log.error("当前充电桩未设置充电模型"); return; } List timeModelList = iTimeModelService.list(Wrappers.query().lambda().eq(TimeModel::getModelId, gunmodel.getId())); List chargeRules = iChargeRuleService.list(Wrappers.query().lambda().eq(ChargeRule::getModelId, gunmodel.getId())); String payload = yxIssueModel(chargeRules, timeModelList, gun, gunmodel); builder.append(payload); String replyMsg = YxObjectUtil.replyMessage(builder.toString(), dto.getSequence(), YxConstant.Cmd.CMD_0A); log.info("JMD-YX计费模型请求应答(CMD = 0A):" + replyMsg); ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); } } } ``` #### 3.4计费模型应答 ```java @Service(value = YxConstant.Service.CMD_57) @AllArgsConstructor @Slf4j public class YxModelListener implements IYXContextListener { private IRechargeGunService iRechargeGunService; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { log.info("嘉满电-YX接收计费模板应答(CMD=57):"+ JSONObject.toJSONString(dto)); String pileNo = StringUtils.substring(dto.getData(), 0, 14); String result = StringUtils.substring(dto.getData(), 14, 16); List gunList = iRechargeGunService.list(Wrappers.query().lambda() .eq(RechargeGun::getPileNo, pileNo)); if(CollectionUtil.isNotEmpty(gunList)){ //计费模型应答成功说明充电抢已经准备就绪,更新充电抢状态 for (int i = 0; i < gunList.size(); i++) { RechargeGun gun = gunList.get(i); if(result.equals("01")){ gun.setRemark("下发成功"); }else{ //失败 gun.setRemark("下发应答失败"); } } iRechargeGunService.updateBatchById(gunList); } } } ``` #### 4.1远程启动充电 ```java public void yxIssueCharge(RechargeGun gun, OrderCharge orderCharge, BigDecimal chargeAmt, Channel channel){ log.info("汽车开始启动下发充电,桩编号 : " + gun.getPileNo() + "枪号 : " + gun.getGunNo()); if (channel != null) { log.info("获取到Channel"); RemoteStartDto dto = new RemoteStartDto(); dto.setGunNo(gun.getGunNo()); dto.setBalance(chargeAmt.multiply(BigDecimal.valueOf(100)).intValue()); dto.setPileNo(gun.getPileNo()); dto.setTradeNo(orderCharge.getOrderNo()); String json = JSON.toJSONString(dto); log.info("JSON串 : " + json); byte[] data = CommonUtil.bytesToString(data); RemoteStartDto dto = JSON.parseObject(data, RemoteStartDto.class); StringBuilder builder = new StringBuilder(); builder.append(StringUtil.leftPad(dto.getTradeNo(), 32, '0')); String pileNo = StringUtil.leftPad(dto.getPileNo(), 14, '0'); builder.append(pileNo); builder.append(StringUtil.leftPad(dto.getGunNo(), 2, '0')); if(StringUtils.isNotBlank(dto.getLogicCardNo())){ builder.append(StringUtil.rightPad(dto.getLogicCardNo(), 16, '0')); }else{ builder.append(StringUtil.rightPad("0",16,'0')); } if(StringUtils.isNotBlank(dto.getPhysicsCardNo())){ builder.append(StringUtil.rightPad(dto.getPhysicsCardNo(), 16, '0')); }else{ builder.append(StringUtil.rightPad("0",16,'0')); } builder.append(CommonUtil.reverseHex(StringUtil.leftPad(Integer.toHexString(dto.getBalance()), 8, '0'))); String replyMsg = YxObjectUtil.pushMessage(builder.toString(), YxConstant.Cmd.CMD_34); log.info("运营平台远程控制下发充电启机(CMD = 34):" + replyMsg); if(client==null) { JSONObject json1 = new JSONObject(); json1.put("pileNo", pileNo); json1.put("body", replyMsg); //下发命令 rocketMQTemplate.asyncSend(mqProperties.getSocketTopic() + ":YX", json1.toJSONString(), RocketmqSendCallbackBuilder.commonCallback()); } ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); } } ``` #### 4.2 启动充电应答 ```java @Service(value = YxConstant.Service.CMD_33) @AllArgsConstructor @Slf4j public class YxRemoteStartReplyListener implements IYXContextListener { private static final int[] number = {32, 14, 2, 2, 2}; private IRechargeGunService iRechargeGunService; private IOrderChargeService iOrderChargeService; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { log.info("开始处理JMD-YX远程启动回复,CMD=33," + dto); int cursor = 0, start = 0, end = number[cursor]; String tradeNo = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String pileNo = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String gunNo = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String startResult = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String failure = StringUtils.substring(dto.getData(), start, end); OrderCharge orderCharge = iOrderChargeService.getOne(Wrappers.query().lambda() .eq(OrderCharge::getOrderNo, CommonUtil.removeZero(tradeNo, true))); if (orderCharge != null) { if ("01".equals(startResult)) { log.info("JMD-YX【" + pileNo + "】启动充电成功"); } else { log.info("JMD-YX【" + pileNo + "】启动充电失败:" + failure + "|" + YxStartErrorEnum.getValueByKey(failure)); iOrderChargeService.handleAbnormalCharge(Func.toStr(orderCharge.getId()), YxStartErrorEnum.getValueByKey(failure)); } } } } ``` #### 5.1下发停止订单 ```java //运营平台远程停机 private Boolean yxStopCharge(OrderCharge order,Channel channel,RechargePile pile){ StringBuilder payload = new StringBuilder(); payload.append(StringUtils.leftPad(pile.getPileNo(),14,"0")) .append(order.getChargeGunNo()); String replyMsg = YxObjectUtil.pushMessage(payload.toString(), YxConstant.Cmd.CMD_36); log.info("JMD-YX下发停止充电(CMD=36)报文:" + replyMsg); if(channel==null) { JSONObject json1 = new JSONObject(); json1.put("pileNo", pile.getPileNo()); json1.put("body", replyMsg); //下发命令 rocketMQTemplate.asyncSend(mqProperties.getSocketTopic() + ":YX", json1.toJSONString(), RocketmqSendCallbackBuilder.commonCallback()); return true; } ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = channel.writeAndFlush(buf); return true; } ``` #### 5.2远程停机命令回复 ```java @Service(value = YxConstant.Service.CMD_35) @AllArgsConstructor @Slf4j public class YxRemoteStopReplyListener implements IYXContextListener { private IRechargeGunService iRechargeGunService; private static final int[] number = {14, 2, 2, 2}; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { log.info("接收JDM-YX远程停止充电命令回复(CMD = 35):" + dto); int cursor = 0, start = 0, end = number[cursor]; String pileNo = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String gunNo = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String result = StringUtils.substring(dto.getData(), start, end); start = end; end += number[++cursor]; String failure = StringUtils.substring(dto.getData(), start, end); RechargeGun rechargeGun = iRechargeGunService.getOne(Wrappers.query().lambda() .eq(RechargeGun::getPileNo, pileNo) .eq(RechargeGun::getGunNo, gunNo)); if (rechargeGun != null) { if (result.equals("01")) { rechargeGun.setWorkStatus(BizConstant.WorkStatus.FREE); }else{ log.error("JMD-YX【"+pileNo+"】停止充电失败:"+result + "|" + YxStopErrorEnum.getValueByKey(failure)); } iRechargeGunService.updateById(rechargeGun); StringBuilder builder = new StringBuilder(); builder.append("35"); builder.append(pileNo); builder.append(rechargeGun.getGunNo()); builder.append("01"); String replyMsg = YxObjectUtil.replyMessage(builder.toString(), dto.getSequence(), YxConstant.Cmd.CMD_0A); ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); } } } ``` #### 6.实时充电 ```java @Service @AllArgsConstructor @Slf4j public class YxActualDataService implements ICarCmdBaseService { public void doExecute(byte[] data, Channel client) { Map dataMap = JSON.parseObject(CommonUtil.bytesToString(data), Map.class); String hexPileNo = StringUtil.leftPad(CommonUtil.hexToString(dataMap.get("pileNo")), 14, '0'); String hexGunNo = StringUtil.leftPad(CommonUtil.hexToString(dataMap.get("gunNo")), 2, '0'); StringBuilder builder = new StringBuilder(); builder.append(hexPileNo).append(hexGunNo); String replyMsg = YxObjectUtil.pushMessage(builder.toString(), YxConstant.Cmd.CMD_12); log.info("读取实时监测数据请求(CMD = 12):" + replyMsg); ByteBuf buf = Unpooled.buffer(replyMsg.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(replyMsg)); ChannelFuture future = client.writeAndFlush(buf); if (future.isDone()) { } } } ``` #### 7.1下发二维码 ```java private R yxCarQrcode(String pileNo, String domain) { Map channels = (Map) redisUtil.get(CacheNames.TCP_CHANNEL); if (channels == null || channels.get(pileNo) == null) { return R.fail("设备已离线"); } Channel client = TcpInboundAdapter.clients.find(channels.get(pileNo)); StringBuilder builder = new StringBuilder(); String qrcodeUrl = "https://" + domain + "/yx?yxNo="; String pN = StringUtil.leftPad(pileNo, 14, '0'); builder.append(pN); builder.append("00"); builder.append(Integer.toHexString(qrcodeUrl.length())); builder.append(YxObjectUtil.stringToAsciiToHex(qrcodeUrl)); String sendMessage = YxObjectUtil.pushMessage(builder.toString(), YxConstant.Cmd.CMD_f0); log.info("发送JMD-YX设置二维码报文[" + pileNo + "](CMD=f0): " + sendMessage); if(client==null){ JSONObject json = new JSONObject(); json.put("pileNo",pileNo); json.put("body",builder.toString()); rocketMQTemplate.asyncSend(mqProperties.getSocketTopic()+":YX", json.toJSONString(), RocketmqSendCallbackBuilder.commonCallback()); return R.success("执行中"); } ByteBuf buf = Unpooled.buffer(sendMessage.getBytes().length); buf.writeBytes(CommonUtil.hexToByteArray(sendMessage)); ChannelFuture success13 = client.writeAndFlush(buf); return R.success("执行中"); } ``` #### 7.2下发二维码应答 ```java @Service(value = YxConstant.Service.CMD_f1) @AllArgsConstructor @Slf4j public class YxQrcodeListener implements IYXContextListener { private static final int[] number = {32, 14}; @Override public void doExecute(YXBasicDto dto, Channel client) throws Exception { log.info("汽车设置二维码应答:"+dto.toString()); } } ``` ### 二、YDBasicDto 云快充处理基类 ```java @Data public class YXBasicDto implements java.io.Serializable { /** * 开始标识 */ private String startPrefix; /** * 数据长度 */ private String dataLength; /** * 序列号域 */ private String sequence; /** * 加密标志 */ private String encodeFlag; /** * 帧类型 */ private String frameType; /** * 消息体 */ private String data; /** * 校验域 */ private String validate; } ``` ### 三、更新channel ```java private void updateCacheByDeviceNo(String deviceNo, Channel client) { ChannelId channelId = client.id(); Map result = (Map) redisUtil.get(CacheNames.TCP_CHANNEL); if (result == null) { result = new HashMap<>(); } result.put(deviceNo, channelId); log.info(deviceNo + "添加到client中,channelId="+clients.find(channelId)); clients.add(client); redisUtil.set(CacheNames.TCP_CHANNEL, result); log.info("======" + deviceNo + "====" + channelId + "===============客户端数量:"+clients.size()+"===redis存储数量:"+result.size()); } ``` ###