package com.jsowell.pile.service; import com.alibaba.fastjson2.JSON; import com.google.common.collect.Lists; import com.jsowell.common.constant.CacheConstants; import com.jsowell.common.constant.Constants; import com.jsowell.common.core.domain.ykc.YKCDataProtocol; import com.jsowell.common.core.redis.RedisCache; import com.jsowell.common.enums.ebike.EBikeChargeResponseEnum; import com.jsowell.common.enums.ykc.ChargingFailedReasonEnum; import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.util.BytesUtil; import com.jsowell.common.util.StringUtils; import com.jsowell.pile.domain.PileBillingTemplate; import com.jsowell.pile.domain.PileFirmwareInfo; import com.jsowell.pile.domain.ebike.deviceupload.ChargingOperationResponse; import com.jsowell.pile.domain.ykcCommond.*; import com.jsowell.pile.dto.PublishBillingTemplateDTO; import com.jsowell.pile.dto.RemoteAccountBalanceUpdateDTO; import com.jsowell.pile.dto.UpdateFirmwareDTO; import com.jsowell.pile.vo.web.BillingTemplateVO; import com.jsowell.pile.vo.web.PileDetailVO; import com.jsowell.wxpay.service.WxAppletRemoteService; import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @Service public class PileRemoteService { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired private PileBillingTemplateService pileBillingTemplateService; @Autowired private PileBasicInfoService pileBasicInfoService; @Autowired private YKCPushCommandService ykcPushCommandService; @Autowired private PileFirmwareInfoService pileFirmwareInfoService; @Autowired private RedisCache redisCache; @Autowired private WxAppletRemoteService wxAppletRemoteService; @Autowired private EBikeSendCommandService eBikeSendCommandService; @Autowired private OrderBasicInfoService orderBasicInfoService; @Value("${remoteUpdate.server}") private String serverAddress; @Value("${remoteUpdate.port}") private int port; @Value("${remoteUpdate.username}") private String username; @Value("${remoteUpdate.password}") private String password; /** * 获取充电桩实时数据信息 * * @param pileSn 充电桩sn * @param connectorCode 枪口号 */ public void getRealTimeMonitorData(String pileSn, String connectorCode) { if (StringUtils.isNotEmpty(pileSn) || StringUtils.isNotEmpty(connectorCode)) { GetRealTimeMonitorDataCommand command = GetRealTimeMonitorDataCommand.builder() .pileSn(pileSn) .connectorCode(connectorCode) .build(); ykcPushCommandService.pushGetRealTimeMonitorDataCommand(command); } } /** * 重启指令 * * @param pileSn 充电桩sn */ public void reboot(String pileSn) { RebootCommand command = RebootCommand.builder().pileSn(pileSn).build(); ykcPushCommandService.pushRebootCommand(command); } /** * 远程启动充电 0x34 * * @param pileSn 充电桩sn */ public void remoteStartCharging(String pileSn, String connectorCode, String transactionCode, BigDecimal chargeAmount) { if (StringUtils.isEmpty(pileSn) || StringUtils.isEmpty(connectorCode)) { log.warn("远程启动充电, 充电桩编号和枪口号不能为空"); return; } // 发送启动充电指令前,再次下发计费模板 2025年2月6日15点01分取消启动前下发计费模板 // BillingTemplateVO billingTemplateVO = pileBillingTemplateService.selectBillingTemplateDetailByPileSn(pileSn); // this.publishPileBillingTemplate(pileSn, billingTemplateVO); // log.info("发送启动充电指令前,再次下发计费模板, transactionCode:{}, 计费模板:{}", transactionCode, JSON.toJSONString(billingTemplateVO)); log.info("【=====平台下发指令=====】: 远程启动充电, 桩号:{}, 枪口号:{}", pileSn, connectorCode); StartChargingCommand startChargingCommand = StartChargingCommand.builder() .pileSn(pileSn) .connectorCode(connectorCode) .transactionCode(transactionCode) .chargeAmount(chargeAmount) .build(); ykcPushCommandService.pushStartChargingCommand(startChargingCommand); } /** * 电单车远程启动充电 */ public void remoteStartChargingEBike(String pileSn, String connectorCode, String transactionCode, BigDecimal chargeAmount) { if (StringUtils.isEmpty(pileSn) || StringUtils.isEmpty(connectorCode)) { log.error("电单车远程启动充电, 充电桩编号和枪口号不能为空"); return; } PileDetailVO pileDetailVO = pileBasicInfoService.selectPileDetailByPileSn(pileSn); if (pileDetailVO == null) { log.error("电单车远程启动充电, 充电桩:{}查询不能为空", pileSn); return; } BillingTemplateVO billingTemplateVO = pileBillingTemplateService.queryUsedBillingTemplateForEBike(pileDetailVO.getStationId()); if (billingTemplateVO == null) { log.error("电单车远程启动充电, 站点id:{}, 充电桩:{}, 没有配置计费模板", pileDetailVO.getStationId(), pileSn); return; } log.info("【=====平台下发指令=====】: 电单车远程启动充电, 桩号:{}, 枪口号:{}", pileSn, connectorCode); EBikeStartChargingCommand startChargingCommand = new EBikeStartChargingCommand(); startChargingCommand.setPileSn(pileSn); startChargingCommand.setConnectorCode(connectorCode); startChargingCommand.setTransactionCode(transactionCode); startChargingCommand.setChargeAmount(chargeAmount); // 支付金额 startChargingCommand.setRateMode(2); // 根据启动金额计算电量 BigDecimal flatElectricityPrice = billingTemplateVO.getFlatElectricityPrice(); BigDecimal electricity = BigDecimal.ZERO; if (flatElectricityPrice.compareTo(BigDecimal.ZERO) > 0) { electricity = chargeAmount.divide(flatElectricityPrice, 2, BigDecimal.ROUND_HALF_UP); } startChargingCommand.setElectricity(electricity); try { ChargingOperationResponse startChargingResponse = eBikeSendCommandService.sendStartChargingCommand(startChargingCommand); log.info("StartChargingResponse:{}", JSON.toJSONString(startChargingResponse)); if (startChargingResponse != null) { int result = startChargingResponse.getResult(); if (result == 0) { // 启动成功 orderBasicInfoService.chargingPileStartedSuccessfully(transactionCode); } else { String failedReasonMsg = EBikeChargeResponseEnum.getDescriptionByCode(result); // 启动失败 682204000001000000000041 orderBasicInfoService.chargingPileFailedToStart(transactionCode, failedReasonMsg); } } } catch (Exception e) { log.error("电单车远程启动充电error", e); } } /** * 远程停止充电 */ public void remoteStopCharging(String pileSn, String connectorCode, String transactionCode) { // 获取充电桩0x13数据,校验交易流水号是否一致 String pileIsChargingKey = CacheConstants.PILE_IS_CHARGING + pileSn + connectorCode; String redisResult = redisCache.getCacheObject(pileIsChargingKey); if (StringUtils.isNotBlank(redisResult) && !StringUtils.equals(redisResult, transactionCode)) { log.info("发送远程停止充电指令-充电桩枪口编号:{}, 获取到正在充电中的交易流水号:{}, 与入参交易流水号:{}不一致, function return", pileSn + connectorCode, redisResult, transactionCode); return; } // 查询桩信息 PileDetailVO pileDetailVO = pileBasicInfoService.selectPileDetailByPileSn(pileSn); if (pileDetailVO == null) { return; } StopChargingCommand command = StopChargingCommand.builder() .pileSn(pileSn) .connectorCode(connectorCode) .transactionCode(transactionCode) .build(); String chargePortType = pileDetailVO.getChargePortType(); if (StringUtils.equals(chargePortType, Constants.THREE)) { // 发送电动自行车桩停止充电指令 log.info("发送电单车桩停止充电指令, chargePortType:{}", chargePortType); eBikeSendCommandService.sendStopChargingCommand(command); } else { // 发送电动汽车桩停止充电指令 log.info("发送汽车桩停止充电指令, chargePortType:{}", chargePortType); ykcPushCommandService.pushStopChargingCommand(command); } log.info("remoteStopCharging success, pileConnectorCode:{}, transactionCode:{}", pileSn + connectorCode, transactionCode); } public void remoteStopChargingForEBike(String pileSn, String connectorCode, String transactionCode) { StopChargingCommand command = StopChargingCommand.builder() .pileSn(pileSn) .connectorCode(connectorCode) .transactionCode(transactionCode) .build(); try { eBikeSendCommandService.sendStopChargingCommand(command); } catch (Exception e) { log.error("remoteStopChargingForEBike error, pileSn:{}, connectorCode:{}, transactionCode:{}" ,pileSn, connectorCode, transactionCode, e); } } /** * 下发充电桩二维码 * * @param pileSn 充电桩sn */ public void issueQRCode(String pileSn) { issueQRCode(pileSn, null); } public void issueQRCode(String pileSn, String qrcodePrefix) { IssueQRCodeCommand command = IssueQRCodeCommand.builder().pileSn(pileSn).build(); if (StringUtils.isNotBlank(qrcodePrefix)) { command.setQrcodePrefix(qrcodePrefix); } ykcPushCommandService.pushIssueQRCodeCommand(command); } /** * 充电桩对时 * * @param pileSn 充电桩sn */ public void proofreadTime(String pileSn) { ProofreadTimeCommand command = ProofreadTimeCommand.builder().pileSn(pileSn).build(); ykcPushCommandService.pushProofreadTimeCommand(command); } /** * 下发充电桩计费模型 */ public void publishPileBillingTemplate(String pileSn, BillingTemplateVO billingTemplateVO) { log.info("下发充电桩计费模型, pileSn:{}, threadName:{}", pileSn, Thread.currentThread().getName()); PublishPileBillingTemplateCommand command = PublishPileBillingTemplateCommand.builder() .billingTemplateVO(billingTemplateVO) .pileSn(pileSn) .build(); ykcPushCommandService.pushPublishPileBillingTemplate(command); } /** * 下发计费模板 * @param dto * @return */ // public boolean publishBillingTemplateOld(PublishBillingTemplateDTO dto) { // // 获取计费模板信息 // BillingTemplateVO billingTemplateVO = pileBillingTemplateService.selectBillingTemplateByTemplateId(dto.getTemplateId()); // if (billingTemplateVO == null) { // log.warn("获取计费模板信息, 通过模板id:{}查询计费模板为null", dto.getTemplateId()); // return false; // } // // 会员优惠计费模板不发布 // if (Constants.ONE.equals(billingTemplateVO.getMemberFlag())) { // return false; // } // // 电单车计费模板不发布 // if (Constants.TWO.equals(billingTemplateVO.getDeviceType())) { // return false; // } // // 更新计费模板的发布时间 // PileBillingTemplate pileBillingTemplate = new PileBillingTemplate(); // pileBillingTemplate.setId(Long.valueOf(billingTemplateVO.getTemplateId())); // pileBillingTemplate.setPublishTime(new Date()); // pileBillingTemplateService.updatePileBillingTemplate(pileBillingTemplate); // // 获取到站点下所有的桩 // List pileList = pileBasicInfoService.selectPileListByStationIds(Lists.newArrayList(Long.valueOf(dto.getStationId()))); // if (CollectionUtils.isNotEmpty(pileList)) { // // 删除缓存 // List collect = pileList.parallelStream() // .map(vo -> CacheConstants.BILLING_TEMPLATE_BY_PILE_SN + vo.getPileSn()) // .collect(Collectors.toList()); // redisCache.deleteObject(collect); // // 下发计费模板, 电单车不支持 // if (StringUtils.equals(billingTemplateVO.getDeviceType(), Constants.ONE)) { // // 下发指令 // pileList.parallelStream().forEach(pileInfoVO -> publishPileBillingTemplate(pileInfoVO.getPileSn(), billingTemplateVO)); // } // } // // 修改计费模板状态 // pileBillingTemplateService.changeStationTemplate(dto.getStationId(), dto.getTemplateId(), billingTemplateVO.getDeviceType(), billingTemplateVO.getMemberFlag()); // return true; // } /** * 下发计费模板 * @param dto * @return */ public boolean publishBillingTemplate(PublishBillingTemplateDTO dto) { // 获取计费模板信息 BillingTemplateVO billingTemplateVO = pileBillingTemplateService.selectBillingTemplateByTemplateId(dto.getTemplateId()); if (billingTemplateVO == null) { log.warn("获取计费模板信息, 通过模板id:{}查询计费模板为null", dto.getTemplateId()); return false; } // 判断是否下发计费模板 默认下发 boolean sendBillingTemplateToPile = true; // 会员优惠计费模板不下发 if (Constants.ONE.equals(billingTemplateVO.getMemberFlag())) { sendBillingTemplateToPile = false; } // 电单车计费模板不下发 if (Constants.TWO.equals(billingTemplateVO.getDeviceType())) { sendBillingTemplateToPile = false; } // 更新计费模板的发布时间 PileBillingTemplate pileBillingTemplate = new PileBillingTemplate(); pileBillingTemplate.setId(Long.valueOf(billingTemplateVO.getTemplateId())); pileBillingTemplate.setPublishTime(new Date()); pileBillingTemplateService.updatePileBillingTemplate(pileBillingTemplate); // 修改计费模板状态 pileBillingTemplateService.changeStationTemplate(dto.getStationId(), dto.getTemplateId(), billingTemplateVO.getDeviceType(), billingTemplateVO.getMemberFlag()); // 下发计费模板, 电单车不支持 if (sendBillingTemplateToPile) { // 获取到站点下所有的桩 List pileList = pileBasicInfoService.selectPileListByStationIds(Lists.newArrayList(Long.valueOf(dto.getStationId()))); if (CollectionUtils.isNotEmpty(pileList)) { // 删除缓存 List collect = pileList.parallelStream() .map(vo -> CacheConstants.BILLING_TEMPLATE_BY_PILE_SN + vo.getPileSn()) .collect(Collectors.toList()); redisCache.deleteObject(collect); // 下发计费模板指令 pileList.parallelStream().forEach(pileInfoVO -> publishPileBillingTemplate(pileInfoVO.getPileSn(), billingTemplateVO)); } } return true; } /** * 远程更新 */ public void updateFirmware(UpdateFirmwareDTO dto) { PileFirmwareInfo pileFirmwareInfo = pileFirmwareInfoService.selectPileFirmwareInfoById(Long.valueOf(dto.getFirmwareId())); if (pileFirmwareInfo == null) { throw new BusinessException(ReturnCodeEnum.CODE_FIRMWARE_NOT_FOUND_ERROR); } String ip; try { String server = StringUtils.removeHttp(serverAddress); ip = InetAddress.getByName(server).getHostAddress(); } catch (UnknownHostException e) { throw new BusinessException("", "无法解析出IP"); } UpdateFirmwareCommand command = UpdateFirmwareCommand.builder() .pileSnList(dto.getPileSns()) .serverAddress(ip) .port(port) .username(username) .password(password) .filePath(pileFirmwareInfo.getFilePath()) .build(); ykcPushCommandService.pushUpdateFileCommand(command); } public static void main(String[] args) { //获取百度IP地址 try { System.out.println("www.baidu.com的地址: "+InetAddress.getByName("www.baidu.com").getHostAddress()); } catch (UnknownHostException e) { throw new RuntimeException(e); } //获取百度IP地址 try { System.out.println("jsowell的地址: "+InetAddress.getByName("apitest.jsowellcloud.com").getHostAddress()); } catch (UnknownHostException e) { throw new RuntimeException(e); } String serverAddress = "http://apitest.jsowellcloud.com"; String ip; try { String server = StringUtils.removeHttp(serverAddress);; ip = InetAddress.getByName(server).getHostAddress(); } catch (UnknownHostException e) { throw new BusinessException("", "无法解析出IP"); } System.out.println("=====" + ip); } /** * 远程账户余额更新 */ public void remoteAccountBalanceUpdate(RemoteAccountBalanceUpdateDTO dto) { RemoteAccountBalanceUpdateCommand command = RemoteAccountBalanceUpdateCommand.builder() .pileSn(dto.getPileSn()) .connectorCode(dto.getConnectorCode()) .accountBalance(dto.getAccountBalance()) .build(); if (StringUtils.isNotBlank(dto.getLogicCard())) { command.setLogicCard(dto.getLogicCard()); } ykcPushCommandService.pushAccountBalanceUpdateCommand(command); } public void remoteControlGroundLock(RemoteControlGroundLockCommand command) { ykcPushCommandService.pushRemoteControlGroundLock(command); } /** * 预约充电指令/预约指令 * @return result: 1-成功; 0-失败 */ public String reservationCharging(ReservationChargingCommand command) { String result = "1"; byte[] bytes = ykcPushCommandService.pushReservationChargingCommand(command); // 解析结果 if (Objects.isNull(bytes)) { result = "0"; } else { YKCDataProtocol ykcDataProtocol = new YKCDataProtocol(bytes); byte[] msgBody = ykcDataProtocol.getMsgBody(); int startIndex = 0; int length = 16; // 交易流水号 byte[] transactionCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); String transactionCode = BytesUtil.bcd2Str(transactionCodeByteArr); // 桩编码 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); // 启动结果 0x00失败 0x01成功 startIndex += length; length = 1; byte[] resultCodeByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); String resultCode = BytesUtil.bcd2Str(resultCodeByteArr); if (StringUtils.equals(resultCode, "00")) { result = "0"; } else { result = "1"; } // 失败原因 startIndex += length; length = 1; byte[] failedReasonByteArr = BytesUtil.copyBytes(msgBody, startIndex, length); String failedReason = BytesUtil.bcd2Str(failedReasonByteArr); String failedReasonMsg = ChargingFailedReasonEnum.getMsgByCode(Integer.parseInt(failedReason, 16)); log.info("0x59预约充电响应sync, 交易流水号:{}, 桩SN:{}, 枪口号:{}, 结果(00-失败; 01成功):{}, 失败原因:{}", transactionCode, pileSn, connectorCode, resultCode, failedReasonMsg); } return result; } }