!33 新增云快充启动充电的逻辑卡号和物理卡号,和双枪并充序号以及双枪并充的测试接口

* 新增云快充启动充电的逻辑卡号和物理卡号,和双枪并充序号以及双枪并充的测试接口
* CMD路由优化
* Merge remote-tracking branch 'gitee/master' into Feat_Lvneng_module_optimize
* cmd路由优化
* cmd路由优化
* 绿能模块优化
* 新增云快充1.7 0x3D
* 添加停止充电的TestController
This commit is contained in:
三丙
2025-08-25 14:04:25 +00:00
parent 7c26534dff
commit 921045af8f
63 changed files with 795 additions and 380 deletions

View File

@@ -0,0 +1,32 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.annotation;
import java.lang.annotation.*;
/**
* 通用协议命令注解
* 所有协议的命令类都应该使用此注解
*
* @author sanbing
* @since 2024-12-16
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ProtocolCmd {
/**
* 命令字值
*/
int value();
/**
* 支持的协议名列表,必须明确指定
*/
String[] protocolNames();
}

View File

@@ -13,10 +13,14 @@ public enum DownlinkCmdEnum {
LOGIN_ACK,
HEARTBEAT_ACK,
VERIFY_PRICING_ACK,
QUERY_PRICING_ACK,
REAL_TIME_DATA_ACK,
SET_PRICING,
REMOTE_START_CHARGING,

View File

@@ -0,0 +1,29 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.executor;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.domain.ProtocolSession;
/**
* 通用协议下行命令执行器接口
*
* @param <T> 下行消息类型
* @author sanbing
* @since 2024-12-16
*/
public interface ProtocolDownlinkCmdExe<T> {
/**
* 执行下行命令
*
* @param session TCP会话
* @param downlinkMessage 下行消息
* @param protocolContext 协议上下文
*/
void execute(ProtocolSession session, T downlinkMessage, ProtocolContext protocolContext);
}

View File

@@ -0,0 +1,29 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.executor;
import sanbing.jcpp.protocol.ProtocolContext;
import sanbing.jcpp.protocol.domain.ProtocolSession;
/**
* 通用协议上行命令执行器接口
*
* @param <T> 上行消息类型
* @author sanbing
* @since 2024-12-16
*/
public interface ProtocolUplinkCmdExe<T> {
/**
* 执行上行命令
*
* @param session TCP会话
* @param uplinkMessage 上行消息
* @param protocolContext 协议上下文
*/
void execute(ProtocolSession session, T uplinkMessage, ProtocolContext protocolContext);
}

View File

@@ -0,0 +1,45 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.mapping;
import sanbing.jcpp.protocol.domain.DownlinkCmdEnum;
/**
* 下行命令转换器接口
*
* 每个协议模块都应该实现此接口,提供从通用下行命令到协议特定命令的转换
*
* @author sanbing
* @since 2024-12-16
*/
public interface DownlinkCmdConverter {
/**
* 将通用下行命令转换为协议特定的命令字
*
* @param downlinkCmd 通用下行命令
* @return 协议特定的命令字,如果不支持该命令则返回 null
*/
Integer convertToCmd(DownlinkCmdEnum downlinkCmd);
/**
* 检查是否支持指定的下行命令
*
* @param downlinkCmd 通用下行命令
* @return 是否支持
*/
default boolean supports(DownlinkCmdEnum downlinkCmd) {
return convertToCmd(downlinkCmd) != null;
}
/**
* 获取协议名称
*
* @return 协议名称
*/
String getProtocolName();
}

View File

@@ -0,0 +1,105 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.protocol.routing;
import cn.hutool.core.util.ClassUtil;
import lombok.extern.slf4j.Slf4j;
import sanbing.jcpp.protocol.annotation.ProtocolCmd;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
/**
* 通用命令路由器
*
* 提供基于协议名+命令字的路由功能,支持多版本协议
*
* @param <T> 命令执行器类型
* @author sanbing
* @since 2025-08-25
*/
@Slf4j
public class ProtocolCommandRouter<T> {
private final Map<String, T> executorMap = new ConcurrentHashMap<>();
/**
* 构造函数
*
* @param scanBaseClass 要扫描的包的代表类
* @param executorFilter 执行器过滤器,判断类是否为目标执行器类型
*/
public ProtocolCommandRouter(Class<?> scanBaseClass, Predicate<Class<?>> executorFilter) {
initializeRoutes(scanBaseClass, executorFilter);
}
/**
* 初始化路由表
*/
private void initializeRoutes(Class<?> scanBaseClass, Predicate<Class<?>> executorFilter) {
Set<Class<?>> cmdClasses = ClassUtil.scanPackageByAnnotation(
ClassUtil.getPackage(scanBaseClass),
ProtocolCmd.class
);
cmdClasses.stream()
.filter(executorFilter)
.forEach(this::registerExecutor);
}
/**
* 注册命令执行器
*/
private void registerExecutor(Class<?> executorClass) {
ProtocolCmd annotation = executorClass.getAnnotation(ProtocolCmd.class);
if (annotation == null) {
log.warn("类 {} 没有 @ProtocolCmd 注解", executorClass.getName());
return;
}
int cmd = annotation.value();
String[] protocolNames = annotation.protocolNames();
try {
@SuppressWarnings("unchecked")
T executor = (T) executorClass.getDeclaredConstructor().newInstance();
for (String protocolName : protocolNames) {
String key = buildKey(protocolName, cmd);
executorMap.put(key, executor);
log.debug("注册命令执行器: {} -> {}", key, executorClass.getSimpleName());
}
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException e) {
log.error("无法实例化命令执行器 {}: {}", executorClass.getName(), e.getMessage());
throw new RuntimeException("Failed to instantiate command executor: " + executorClass.getName(), e);
}
}
/**
* 获取命令执行器
*
* @param protocolName 协议名称
* @param cmd 命令字
* @return 命令执行器,如果未找到则返回 null
*/
public T getExecutor(String protocolName, int cmd) {
String key = buildKey(protocolName, cmd);
return executorMap.get(key);
}
/**
* 构建路由键
*/
private String buildKey(String protocolName, int cmd) {
return protocolName + ":" + cmd;
}
}