目录
导言
功能简介
本地授权列表
类型
IdToken
IdTagInfo
授权状态
ChargePointErrorCode
CiString50Type
充电桩状态-ChargePointStatus
远程启动停止状态 -RemoteStartStopStatus
协议指令
1、授权-Authorize
1.1 说明
1.2 Authorize.req
1.3 Authorize.conf
1.4 JSON格式
1.5 代码
2、启动通知-BootNotification
2.1 说明
2.2 BootNotification.req
2.3 BootNotification.conf
2.4 JSON格式
2.5 代码
3、心跳-HeartBeat
3.1 说明
3.2 Heartbeat.req
3.3 Heartbeat.conf
3.4 JSON格式
3.5 代码
4、状态通知-StatusNotification
4.1 说明
4.2 StatusNotification.req
4.3 StatusNotification.conf
4.4 JSON格式
4.5 代码
5、远程启动-RemoteStartTransaction
5.1 说明
5.2 RemoteStartTransaction.req
5.3 RemoteStartTransaction.conf
5.4 JSON格式
5.5 代码
6、远程停止-RemoteStopTransaction
6.1 说明
6.2 RemoteStopTransaction.req
6.3 RemoteStopTransaction.conf
6.4 JSON格式
6.5 代码
通用方法
1、转换时间为UTC格式
如 本地授权列表 所述,充电点可在不涉及中央系统的情况下在本地授权标识符。如果用户提交的 idTag 不在本地
它包含充电点发送到中央系统的 Authorize.req PDU 的字段定义。
【SEND】♂[2,"yiBSjMizbzFWUcmz","Authorize",{"idTag":"F3F7C00B"}]
【RECV】♀[3,"yiBSjMizbzFWUcmz",{"idTagInfo":{"status":"Accepted"}}]
逻辑处理
/**
* 授权
* @param data
* @param msgId
* @param msgType
* @param
* @param
* @return
*/
public ReturnData Authorize(String data,String msgId,
String msgType,String ip,String action,String body){
// 桩子 发起的充电,都要走授权
ReturnData returnData=new ReturnData();
try {
// 根据ip查询对应的设备
String equSql="select * from jk_equ where ip='"+ip+"' order by communication_time desc ";
Record equRecord=Db.findFirst(equSql);
if(null!=equRecord){
// [2,"m3MxfgtUXBR1QoyY","Authorize",{"idTag":"43AA3CA6"}]
JSONObject idTagObj=JSONObject.parseObject(data);
// 卡号 43AA3CA6
String idTag=idTagObj.getString("idTag");
IdTagInfo info=new IdTagInfo();
info.setStatus("Accepted");
// 时间推迟 60分钟
Date endDate=DateUtil.getAfterMinute(new Date(),60);
String utcStr=DateUtil.localToUTC(endDate);
info.setExpiryDate(utcStr);
List resultList=new ArrayList();
resultList.add(Integer.parseInt(msgType)+1);
resultList.add(msgId);
AuthorizeConfig authorizeConfig=new AuthorizeConfig();
authorizeConfig.setIdTagInfo(info);
resultList.add(authorizeConfig);
returnData.setResult(true);
log.info("刷卡返回信息:{}",JSON.toJSONString(resultList));
String json=new ObjectMapper().writeValueAsString(resultList);
returnData.setData(json);
}else{
log.error("收到充电点反馈,并且开启事务异常:设备不在线");
returnData.setMsg("设备不在线");
returnData.setResult(false);
}
}catch (Exception e){
log.error("收到充电点反馈,并且开启事务异常",e);
returnData.setResult(false);
}
return returnData;
}
实体类
1、IdTagInfo
import lombok.Data;
/**
*
* 授权token回复详细信息
*/
@Data
public class IdTagInfo {
// Accepted 允许使用可充电的标识符
// Blocked 标识符已被阻止。不允许充电。
// Expired 标识符已过期。不允许充电。
// Invalid 标识符未知。不允许充电。
// ConcurrentTx 标识符已涉及到另一个事务中,并且不允许有多个事务。(仅与StartTransaction.req相关。)
private String status;
// 这其中包含idTag应该从授权缓存中删除的日期。
private String expiryDate;
//private IdToken idToken;//父级标识符。
}
2、AuthorizeConfig
import lombok.Data;
/**
* 授权配置
*/
@Data
public class AuthorizeConfig {
private IdTagInfo idTagInfo;
}
中文图
中文图
英文图
[2,\"e3ba4698-d64b-447e-81f5-0bf0e09700eb\",\"BootNotification\",{\"chargePointVendor\":\"\",\"chargePointModel\":\"\",\"chargePointSerialNumber\":\"\",\"chargeBoxSerialNumber\":\"\",\"firmwareVersion\":\"\",\"iccid\":\"\",\"imsi\":\"\",\"meterType\":\"\",\"meterSerialNumber\":\"\"}]
[3, "e3ba4698-d64b-447e-81f5-0bf0e09700eb", {"status": "Accepted", "interval": 30, "currentTime": "2023-10-10T03:37:53Z"}]
1.逻辑处理
/**
* 设备启动通知
* @param data
* @param msgId
* @param msgType
* @param action
* @param ip
* @return
*/
public ReturnData BootNotification(String data,String msgId,String msgType,String action,String ip,String body){
ReturnData returnData=new ReturnData();
try {
equHex=equHex.replaceAll("/","");
BootNotificationReq req=JSON.parseObject(data,BootNotificationReq.class);
String chargePointSerialNumber=req.getChargePointSerialNumber();//充电桩编号
log.info("sn的Hex{}",chargePointSerialNumber);
String states="Accepted";// 默认拒绝
BootNotificationConf conf=new BootNotificationConf();
String utcDateStr=DateUtil.localToUTC(new Date());
conf.setCurrentTime(utcDateStr); //过期时间
conf.setInterval(30);
conf.setStatus(states);
List resultList=new ArrayList();
resultList.add(Integer.parseInt(msgType)+1);
resultList.add(msgId);
resultList.add(conf);
String json = new ObjectMapper().writeValueAsString(resultList); //用此方法转为json字符串
returnData.setData(json);
returnData.setResult(true);
}catch (Exception e){
log.error("启动通知异常",e);
returnData.setResult(false);
}
return returnData;
}
2. BootNotificationReq
import lombok.Data;
/**
* 启动通知req
*/
@Data
public class BootNotificationReq {
//可选。它包含一个值,用来标识充电点内的充电箱的序列号。已弃用
//,将在未来的版本中被删除
private String chargeBoxSerialNumber;
//需要。这包含一个标识字符点模型的值。
private String chargePointModel;//充电桩模型
//可选。这其中包含一个标识充电点序列号的值。
private String chargePointSerialNumber;
//需要。这包含一个标识特征点的供应商的值
private String chargePointVendor;
//可选。这其中包含了充电点的固件版本
private String firmwareVersion;
//可选。其中包含调制解调器SIM卡的ICCID。
private String iccid;
//可选。其中包含调制解调器SIM卡的IMSI。
private String imsi;
//可选。其中包含充电点的主电表的序列号。
private String meterSerialNumber;
//可选。这包含了充电点的主电表的类型。
private String meterType;
}
3. BootNotificationConf
import lombok.Data;
/**
* 启动通知回复
*/
@Data
public class BootNotificationConf {
// Accepted 充电点已被中央系统接受。
//Pending 中央系统还没有准备好接受充电点。中央系统可以发送信息来检索信息或准备充电点。
// Rejected 中央系统不接受充电点。当中央系统不知道充电点id时,可能会发生这种情况以上翻译结果来自有道神经网络翻译(YNMT)· 通用场景
private String status;
//需要。这包含了中央系统的当前时间。
private String currentTime;
//需要。当接受注册状态时,它包含以秒为单位的心跳间隔。如果中央系统返回的不
//是“接受”,则间隔字段的值表示发送下一个引导通知请求之前的最小等待时间
private Integer interval;
}
未定义字段
它包含中央系统为响应 Heartbeat.req PDU 而发送给充电点的 Heartbeat.conf PDU 的字段定义。
中文
英文
[SEND ->] [2,"QsGAIVnh39SDvmzg","Heartbeat",{}]
[RECV <-] [3,"QsGAIVnh39SDvmzg",{"currentTime":"2023-05-17T14:26:10.306+08:00"}]
1、逻辑
/**
* 设备心跳
* @return
*/
public ReturnData equheartbeat(String data,String msgType,
String msgId,String action,String ip,String body) {
ReturnData returnData = new ReturnData();
try {
log.info("设备心跳{}",data);
if(null!=ip && !"".equals(ip)){
List resultList=new ArrayList();
resultList.add(Integer.parseInt(msgType)+1);
resultList.add(msgId);
Mapmap=new HashMap<>();
Heartbeat heartbeat=new Heartbeat();
String utcStr=DateUtil.localToUTC(new Date());
heartbeat.setCurrentTime(utcStr);
map.put(action,heartbeat);
resultList.add(heartbeat);
returnData.setData(JSON.toJSONString(resultList));
returnData.setResult(true);
}
}else{
returnData.setMsg("设备心跳异常,无法获取ip");
returnData.setCode("111111");
returnData.setResult(false);
return returnData;
}
} catch (Exception e) {
returnData.setMsg("设备心跳异常"+e);
returnData.setCode("111111");
returnData.setResult(false);
e.printStackTrace();
}
return returnData;
}
2、Heartbeat
import lombok.Data;
/**
* 设备心跳
*/
@Data
public class Heartbeat {
private String currentTime;
}
插枪
[SEND ->] [2,"I5622jdZvMI5nrqc","StatusNotification",{"connectorId":1,"errorCode":"NoError","status":"Preparing","timestamp":"2023-08-14T05:31:03Z"}][RECV <-] [3,"I5622jdZvMI5nrqc","{}"]
拔枪
[SEND ->] [2,"5KaXyxFzqamWjDJ3","StatusNotification",{"connectorId":1,"errorCode":"NoError","status":"Available","timestamp":"2023-08-14T06:47:10Z"}]
[3,"5KaXyxFzqamWjDJ3","{}"]
/**
* 状态通知 data,msgType,msgId,action
* @param data
* @param msgType
* @param msgId
* @param action
* @return
*/
public ReturnData statusNotification(String data, String msgType,String msgId,
String action,String ip,String body) {
log.info("设备开始充电状态上报{}",data);
ReturnData returnData = new ReturnData();
String startInfo="";
StatusNotification statusNotification=JSON.parseObject(data,StatusNotification.class);
boolean start=false;
if ("Available".equals(statusNotification.getStatus())
|| "Preparing".equals(statusNotification.getStatus())
|| "Charging".equals(statusNotification.getStatus() )
){ //0x00失败 0x01成功
start=true;
}else {
start=false;
startInfo=statusNotification.getStatus();
}
if (start){
log.info("设备开始充电状态上报信息{}");
}else {
log.info("设备开始充电状态上报,失败代码{},失败详情{}",statusNotification.getStatus(),startInfo);
}
List resultList=new ArrayList();
resultList.add(Integer.parseInt(msgType)+1);
resultList.add(msgId);
resultList.add(new ArrayList<>());
returnData.setData(JSON.toJSONString(resultList));
returnData.setResult(true);
return returnData;
}
StatusNotification
/**
* 发起充电回调
*/
@Data
public class StatusNotification {
private Integer connectorId;//通道ID
private String errorCode;//错误代码
// Available 第一次收到充电命令,且可用
// Preparing 不是第一次收到充电命令,但是可用
// Charging 当连接器的接触器关闭时,允许车辆充电(操作性)
// SuspendedEVSE 当电动汽车连接到EVSE,但EVSE没有向电动汽车提供能源时,例如由于智能充电限制、本地供电电源限制,或由于StartTransaction.conf表明不允许充电等。(操作性的)
// SuspendedEV 当电动汽车连接到EVSE时,EVSE会提供能源,但电动汽车不吸收任何能源。(操作性的)
private String status;//状态
private String timestamp;//时间戳
}
[RECV <-] [2,"4f08e280-b5b2-4197-82ef-f10a1f351fcb","RemoteStartTransaction",{"connectorId":1,"idTag":"C35CEA09"}]
[SEND ->] [3,"4f08e280-b5b2-4197-82ef-f10a1f351fcb",{"status":"Accepted"}]
业务逻辑太多,请查看远程启停充电更新文档;
事务id-枪号
[RECV <-] [2,"28f7d946-c019-4071-8800-8e07426836f2","RemoteStopTransaction",{"transactionId":12023}]
[SEND ->] [3,"28f7d946-c019-4071-8800-8e07426836f2",{"status":"Accepted"}]
业务逻辑太多,请查看远程启停充电更新文档;
public static String localToUTC(Date localDate) {
long localTimeInMillis=localDate.getTime();
/** long时间转换成Calendar */
Calendar calendar= Calendar.getInstance();
calendar.setTimeInMillis(localTimeInMillis);
/** 取得时间偏移量 */
int zoneOffset = calendar.get(java.util.Calendar.ZONE_OFFSET);
/** 取得夏令时差 */
int dstOffset = calendar.get(java.util.Calendar.DST_OFFSET);
/** 从本地时间里扣除这些差量,即可以取得UTC时间*/
calendar.add(java.util.Calendar.MILLISECOND, -(zoneOffset + dstOffset));
/** 取得的时间就是UTC标准时间 */
Date utcDate=new Date(calendar.getTimeInMillis());
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String timeStr=df.format(utcDate);
return timeStr;
}