From e1467baf2b92e50b7c77b013033f1b4d21d090ef Mon Sep 17 00:00:00 2001 From: shiyi Date: Thu, 16 Jan 2025 15:36:41 +0800 Subject: [PATCH] =?UTF-8?q?[v0.0.1]=20=E7=A7=BB=E6=A4=8D=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E4=B8=8A=E7=BA=BF=E3=80=81=E8=AE=A4=E8=AF=81=E3=80=81?= =?UTF-8?q?=E9=87=8D=E8=BF=9E=E5=8A=9F=E8=83=BD=EF=BC=9B=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?uavId,fkId,=E5=93=88=E5=8B=83sn=E7=9A=84=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=9B=E5=A2=9E=E5=8A=A0HTTP\JSON=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=B1=BB=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- pom.xml | 42 +- src/main/java/com/platform/cac/CacHpApi.java | 214 +++++++ .../java/com/platform/cac/GcsService.java | 248 ++++++++ .../java/com/platform/cac/RemoteService.java | 184 ++++++ .../java/com/platform/cac/tcp/CacClient.java | 115 ++++ .../cac/tcp/CacConnectionHandler.java | 107 ++++ .../tcp/codec/RemoteTcpBaseDataDecoder.java | 101 ++++ .../tcp/codec/RemoteTcpBaseDataEncoder.java | 42 ++ .../tcp/message/RemoteMessageDispatcher.java | 64 +++ .../RemoteMessageHandlerContainer.java | 47 ++ .../dataframe/RemoteTcpBaseDataFrame.java | 41 ++ .../receive/CacReceiveUavControlMessage.java | 42 ++ .../receive/CacReplyUavControlMessage.java | 46 ++ .../receive/CacUavCommandMessage.java | 41 ++ .../receive/CacUavControlApplyMessage.java | 42 ++ .../receive/TcpRemoteAuthorization.java | 37 ++ .../send/TcpFlightPlanReplyRequest.java | 21 + .../send/TcpFlightPlanRevokeRequest.java | 20 + .../dataframe/send/TcpFlyReplyRequest.java | 21 + .../dataframe/send/TcpHeartBeatRequest.java | 17 + .../send/TcpReceiveAlarmRequest.java | 19 + .../dataframe/send/TcpReceiveAtcRequest.java | 18 + .../send/TcpRemoteAuthCacRequest.java | 14 + .../handler/CacUavCommandIssuedHandler.java | 58 ++ .../handler/CacUavCommandResultHandler.java | 57 ++ .../handler/CacUavControlApplyHandler.java | 60 ++ .../handler/GcsAuthResponseHandler.java | 46 ++ .../handler/IRemoteMessageHandler.java | 25 + .../com/platform/config/CacRemoteConfig.java | 15 + .../com/platform/config/ServiceConfig.java | 2 +- .../platform/controller/ClientController.java | 4 +- .../java/com/platform/info/GlobalData.java | 40 ++ .../platform/info/enums/ClientTypeEnum.java | 37 ++ .../com/platform/info/enums/GcsFrameEnum.java | 65 +++ .../com/platform/info/enums/GcsTypeEnum.java | 41 ++ .../platform/info/enums/IClientFrameEnum.java | 10 + .../platform/info/enums/RemoteFrameEnum.java | 71 +++ .../com/platform/info/enums/UavTypeEnum.java | 67 +++ .../platform/info/mapping/HaborUavMap.java | 55 ++ .../info/mapping/UavIdControlMap.java | 100 ++++ .../com/platform/info/mapping/UavIdMap.java | 113 ++++ .../platform/model/DirectControlUavParam.java | 11 + src/main/java/com/platform/model/Result.java | 17 + .../platform/service/InMessageHandler.java | 2 +- .../com/platform/service/ServerService.java | 2 +- .../platform/util/BaseAuthorizationUtils.java | 50 ++ .../java/com/platform/util/ByteUtils.java | 543 ++++++++++++++++++ .../com/platform/util/HttpClientUtils.java | 147 +++++ .../java/com/platform/util/JSONUtils.java | 100 ++++ src/main/resources/application-dev.yml | 32 +- src/main/resources/log4j2.yml | 49 -- 52 files changed, 3287 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/platform/cac/CacHpApi.java create mode 100644 src/main/java/com/platform/cac/GcsService.java create mode 100644 src/main/java/com/platform/cac/RemoteService.java create mode 100644 src/main/java/com/platform/cac/tcp/CacClient.java create mode 100644 src/main/java/com/platform/cac/tcp/CacConnectionHandler.java create mode 100644 src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataDecoder.java create mode 100644 src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataEncoder.java create mode 100644 src/main/java/com/platform/cac/tcp/message/RemoteMessageDispatcher.java create mode 100644 src/main/java/com/platform/cac/tcp/message/RemoteMessageHandlerContainer.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/RemoteTcpBaseDataFrame.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReceiveUavControlMessage.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReplyUavControlMessage.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavCommandMessage.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavControlApplyMessage.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/receive/TcpRemoteAuthorization.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanReplyRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanRevokeRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlyReplyRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpHeartBeatRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAlarmRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAtcRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpRemoteAuthCacRequest.java create mode 100644 src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandIssuedHandler.java create mode 100644 src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandResultHandler.java create mode 100644 src/main/java/com/platform/cac/tcp/message/handler/CacUavControlApplyHandler.java create mode 100644 src/main/java/com/platform/cac/tcp/message/handler/GcsAuthResponseHandler.java create mode 100644 src/main/java/com/platform/cac/tcp/message/handler/IRemoteMessageHandler.java create mode 100644 src/main/java/com/platform/config/CacRemoteConfig.java create mode 100644 src/main/java/com/platform/info/GlobalData.java create mode 100644 src/main/java/com/platform/info/enums/ClientTypeEnum.java create mode 100644 src/main/java/com/platform/info/enums/GcsFrameEnum.java create mode 100644 src/main/java/com/platform/info/enums/GcsTypeEnum.java create mode 100644 src/main/java/com/platform/info/enums/IClientFrameEnum.java create mode 100644 src/main/java/com/platform/info/enums/RemoteFrameEnum.java create mode 100644 src/main/java/com/platform/info/enums/UavTypeEnum.java create mode 100644 src/main/java/com/platform/info/mapping/HaborUavMap.java create mode 100644 src/main/java/com/platform/info/mapping/UavIdControlMap.java create mode 100644 src/main/java/com/platform/info/mapping/UavIdMap.java create mode 100644 src/main/java/com/platform/model/DirectControlUavParam.java create mode 100644 src/main/java/com/platform/model/Result.java create mode 100644 src/main/java/com/platform/util/BaseAuthorizationUtils.java create mode 100644 src/main/java/com/platform/util/ByteUtils.java create mode 100644 src/main/java/com/platform/util/HttpClientUtils.java create mode 100644 src/main/java/com/platform/util/JSONUtils.java delete mode 100644 src/main/resources/log4j2.yml diff --git a/.gitignore b/.gitignore index 54f62de..85636c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target/ -/.idea/ \ No newline at end of file +/.idea/ +/log/ diff --git a/pom.xml b/pom.xml index 2740ab6..e77d43d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.5.RELEASE + 2.1.4.RELEASE com.platform @@ -37,33 +37,18 @@ io.netty netty-all - 4.1.32.Final + 4.1.86.Final com.alibaba fastjson - 1.2.76 + 1.2.83 org.springframework.boot spring-boot-starter - - - org.springframework.boot - spring-boot-starter-logging - - - - - - org.springframework.boot - spring-boot-starter-log4j2 - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml + org.springframework.boot spring-boot-starter-actuator @@ -79,12 +64,25 @@ 2.7.0 - org.springframework.boot - spring-boot-starter-data-redis + org.apache.commons + commons-pool2 + + + com.squareup.okhttp3 + okhttp + 3.6.0 + + + + com.google.android + android + + org.apache.commons - commons-pool2 + commons-lang3 + 3.12.0 diff --git a/src/main/java/com/platform/cac/CacHpApi.java b/src/main/java/com/platform/cac/CacHpApi.java new file mode 100644 index 0000000..d7b52e8 --- /dev/null +++ b/src/main/java/com/platform/cac/CacHpApi.java @@ -0,0 +1,214 @@ +package com.platform.cac; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import com.platform.info.GlobalData; +import com.platform.model.DirectControlUavParam; +import com.platform.model.Result; +import com.platform.util.HttpClientUtils; +import com.platform.util.JSONUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.net.ConnectException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.platform.util.BaseAuthorizationUtils.generateAuthAndDateHeader; + + +@Slf4j +@Component +public class CacHpApi { + + private static final String HTFP_PATH = "/htfp/gcs"; + public static String HOST; + @Value("${http-cac.host}") + public void setHost(String host) { + CacHpApi.HOST = host; + log.info(">>>中心指控地址 {}<<<", host); + } + + private static String postRequest(String api, String body) { + String path = "http://" + HOST + api; + HashMap header = new HashMap<>(3); + generateAuthAndDateHeader(header, "POST", GlobalData.GCS_ID, GlobalData.GCS_TOKEN, api); + try { + return HttpClientUtils.sendPost(path, body, header); + } catch (ConnectException e) { + log.error("http请求超时: {}", e.getMessage()); + return null; + } catch (IOException e) { + log.error("http请求错误: {}", e.getMessage()); + return null; + } + } + private static Result postRequestAndGetResult(String api, String body) { + String path = "http://" + HOST + api; + HashMap header = new HashMap<>(3); + generateAuthAndDateHeader(header, "POST", GlobalData.GCS_ID, GlobalData.GCS_TOKEN, api); + try { + return HttpClientUtils.sendPost(path, body, header, Result.class); + } catch (ConnectException e) { + log.error("http请求超时: {}", e.getMessage()); + return null; + } catch (Exception e) { + log.error("http请求错误: {}, body={}", e.getMessage(), body); + return null; + } + } + // /** + // * 响应体解析 + // * + // * @param responseStr + // * @return + // */ + // public static JSONObject checkResponse(String responseStr) { + // JSONObject responseJson = JSON.parseObject(responseStr); + // if (responseJson == null) { + // return null; + // } + // + // Boolean success = responseJson.getBoolean("success"); + // if (!success) { + // log.error("HTTP请求响应失败, code:{}, message: {}", responseJson.getInteger("code"), responseJson.getString("message")); + // } else { + // log.debug("HTTP请求响应成功, code:{}, message: {}", responseJson.getInteger("code"), responseJson.getString("message")); + // } + // return responseJson.getJSONObject("data"); + // } + + /** + * 地面站上线 + */ + public static String gcsSignIn(String body) { + final String gcsSignApi = HTFP_PATH + "/signIn"; + Result result = postRequestAndGetResult(gcsSignApi, body); + if (result == null || !result.isSuccess()) { + log.error("地面站上线请求失败: {}", result); + return null; + } + return result.getData().toString(); + } + + /** + * 地面站下线 (哈勃不下线) + */ + // public static String gcsSignOut(String body) { + // final String gcsSignApi = HTFP_PATH + "/signOut"; + // return postRequest(gcsSignApi, body); + // } + + /** + * 无人机下电 + */ + public static String uavPowerOff(String body) { + final String gcsSignApi = HTFP_PATH + "/uavPowerOff"; + return postRequest(gcsSignApi, body); + } + + /** + * 地面站快照查询 + */ + public static String queryGcsSnapshot(String body) { + final String gcsSignApi = HTFP_PATH + "/queryGcsSnapshot"; + return postRequest(gcsSignApi, body); + } + + /** + * 查询在控飞机 + */ + public static String queryGcsControlUav(String body) { + final String gcsSignApi = HTFP_PATH + "/queryGcsControlUav"; + return postRequest(gcsSignApi, body); + } + + /** + * 异常退出 + */ + public static String gcsExceptionOut(String body) { + final String gcsSignApi = HTFP_PATH + "/exceptionOut"; + return postRequest(gcsSignApi, body); + } + + /** + * 断线重连 + */ + public static String gcsReconnect(String body) { + final String gcsSignApi = HTFP_PATH + "/reconnect"; + return postRequest(gcsSignApi, body); + } + + /** + * 指令执行结果通知 + */ + public static String commandNotify(String body) { + final String gcsSignApi = HTFP_PATH + "/notifyUavCommand"; + return postRequest(gcsSignApi, body); + } + + public static String uavControlApply(String body) { + final String gcsSignApi = HTFP_PATH + "/applyUavControlRight"; + return postRequest(gcsSignApi, body); + } + + public static String uavMasterControlNotify(String body) { + final String gcsSignApi = HTFP_PATH + "/notifyGetUavControlRight"; + return postRequest(gcsSignApi, body); + } + public static String uavControlReply(String body) { + final String gcsSignApi = HTFP_PATH + "/replyUavControlRight"; + return postRequest(gcsSignApi, body); + } + + public static String uavControlReceive(String body) { + final String gcsSignApi = HTFP_PATH + "/receiveUavControlRight"; + return postRequest(gcsSignApi, body); + } + + public static String queryUavId(String body) { + final String gcsSignApi = HTFP_PATH + "/queryUavIdByFlightControlSnAndType"; + return postRequest(gcsSignApi, body); + } + + public static String queryUavMapping(String body) { + final String gcsSignApi = HTFP_PATH + "/queryUavIdAndFlightControlSnMapping"; + return postRequest(gcsSignApi, body); + } + + public static List queryAllCacDirectControlUavList(String body) { + final String gcsSignApi = HTFP_PATH + "/queryAllCacDirectControlUavList"; + try { + Result result = postRequestAndGetResult(gcsSignApi, body); + if (result == null || !result.isSuccess()){ + log.error("查询全量无人机映射关系失败: {}", result); + return null; + } + return JSONUtils.json2list(result.getData().toString(), DirectControlUavParam.class); + } catch (Exception e) { + log.error("查询全量无人机映射关系失败: {}", e.getMessage()); + } + return null; + } + + public static DirectControlUavParam queryCacDirectControlUav(String body) { + final String gcsSignApi = HTFP_PATH + "/queryCacDirectControlUav"; + try { + Result result = postRequestAndGetResult(gcsSignApi, body); + if (result == null || !result.isSuccess()){ + log.error("查询单个无人机映射关系失败,body={}, result={}", body, result); + return null; + } + return JSONUtils.json2obj(result.getData().toString(), DirectControlUavParam.class); + } catch (Exception e) { + log.error("查询全量无人机映射关系失败: {}", e.getMessage()); + } + return null; + } +} diff --git a/src/main/java/com/platform/cac/GcsService.java b/src/main/java/com/platform/cac/GcsService.java new file mode 100644 index 0000000..9d4904d --- /dev/null +++ b/src/main/java/com/platform/cac/GcsService.java @@ -0,0 +1,248 @@ +package com.platform.cac; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import com.platform.cac.tcp.CacClient; +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.cac.tcp.message.dataframe.send.TcpRemoteAuthCacRequest; +import com.platform.info.GlobalData; +import com.platform.info.enums.ClientTypeEnum; +import com.platform.info.enums.GcsFrameEnum; +import com.platform.info.enums.GcsTypeEnum; +import com.platform.info.enums.RemoteFrameEnum; +import com.platform.model.Result; +import com.platform.util.JSONUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @Author : shiyi + * @Date : 2024/1/3 15:08 + * @Description : 对地面站业务逻辑的封装 + * + * // TODO 2024/11/28: 方法归类有点混乱,与地面站的交互和与中心指控的交互应该分开 + */ +@Component +@Slf4j +public class GcsService { + @Resource + CacClient cacClient; + @Resource + RemoteService remoteService; + + + public static Channel gcsChannel; + + // /** + // * 向地面站发送消息 + // * + // * @param type 帧类型编码 + // * @param data 数据内容(除去帧头、帧类型) + // * @return + // */ + // public ChannelFuture sendToGcs(byte type, byte[] data) { + // GcsMessage gcsMessage = new GcsMessage(); + // gcsMessage.setHead(ClientTypeEnum.GCS.getWriteHead()); + // gcsMessage.setType(type); + // gcsMessage.setReadableDataBytes(data); + // if (gcsChannel != null && gcsChannel.isActive()) { + // return gcsChannel.writeAndFlush(gcsMessage); + // } else { + // log.warn("地面站消息发送失败: 与地面站握手未成功"); + // } + // return null; + // } + // /** + // * 向地面站发送消息 + // * + // * @param type 帧类型编码 + // * @param buf 数据内容(除去帧头、帧类型) + // * @return + // */ + // public ChannelFuture sendToGcs(byte type, ByteBuf buf) { + // GcsMessage gcsMessage = new GcsMessage(); + // gcsMessage.setHead(ClientTypeEnum.GCS.getWriteHead()); + // gcsMessage.setType(type); + // gcsMessage.setReadableDataBytes(ByteBufUtil.getBytes(buf)); + // buf.release(); + // if (gcsChannel != null && gcsChannel.isActive()) { + // return gcsChannel.writeAndFlush(gcsMessage); + // } else { + // log.warn("地面站消息发送失败: 与地面站握手未成功"); + // } + // return null; + // } + + /** + * 构建指控系统的数据帧 + * @param dataframe 基本数据帧 + * @param type 数据帧类型 + */ + public void buildRemoteBaseDataFrame(RemoteTcpBaseDataFrame dataframe, byte type) { + dataframe.setMagicCode(GlobalData.REMOTE_HEAD); + dataframe.setVersion(GlobalData.REMOTE_VERSION2); + dataframe.setSerializationAlgorithm(GlobalData.REMOTE_SER_ALG); + dataframe.setType(type); + dataframe.setGcsIdLength((byte) GlobalData.GCS_ID.length()); + dataframe.setGcsId(GlobalData.GCS_ID); + dataframe.setGcsAuthLength((byte) GlobalData.AUTHORIZATION.length()); + dataframe.setGcsAuth(GlobalData.AUTHORIZATION); + } + + + + public boolean isSignIn() { + if (GlobalData.GCS_SIGNIN) { + return true; + } else { + log.warn("地面站未上线!"); + return false; + } + } + + + public boolean gcsSignInRequest(){ + if (GlobalData.GCS_SIGNIN) { + log.info("已登录中心指控,无需重复登录"); + return true; + } + JSONObject body = new JSONObject(); + body.put("gcsId", GlobalData.GCS_ID); + try { + GlobalData.AUTHORIZATION = CacHpApi.gcsSignIn(body.toString()); + if (GlobalData.AUTHORIZATION != null) { + log.info("地面站上线请求成功,认证中..."); + } + return GlobalData.AUTHORIZATION != null; + } catch (Exception e) { + e.printStackTrace(); + log.error("地面站上线请求失败: {}", e.getMessage()); + return false; + } + + } + + + + /** + * 地面站上线tcp认证 + */ + public void gcsAuthRequestToCtrl() { + TcpRemoteAuthCacRequest tcpGcsAuthCacRequest = new TcpRemoteAuthCacRequest(); + ChannelFuture sendFuture = null; + try { + if (StringUtils.isEmpty(GlobalData.AUTHORIZATION)) { + log.info("[tcpAuth] authorization 为空,重新发起上线请求"); + gcsSignInRequest(); + } + buildRemoteBaseDataFrame(tcpGcsAuthCacRequest, RemoteFrameEnum.GCS_AUTH_CAC_REQUEST.getCode()); + tcpGcsAuthCacRequest.setUavIdLength((byte) 0); + tcpGcsAuthCacRequest.setUavId(""); + tcpGcsAuthCacRequest.setReadableDataBytesLength((byte) 0); + tcpGcsAuthCacRequest.setReadableDataBytes(null); + + if (cacClient.channel.isActive()) { + sendFuture = cacClient.channel.writeAndFlush(tcpGcsAuthCacRequest).sync(); + if (sendFuture.isSuccess()){ + log.info("[tcpAuth] 认证请求已成功发送发送,认证码:{}", GlobalData.AUTHORIZATION); + } + } else { + log.error("[tcpAuth] 和中心指控之间tcp连接异常,tcp认证发送失败!"); + } + } catch (Exception e) { + log.info("[tcpAuth] tcp认证数据构建失败 {}", e); + } + if (sendFuture == null || !sendFuture.isSuccess()) { + // 如果发送失败,则重复验证 + cacClient.channel.eventLoop().schedule(this::gcsAuthRequestToCtrl, 3, TimeUnit.SECONDS); + } + } + + + /** + * 申请单个无人机控制权 + * @param fkUavId 飞控id + * @return 是否申请成功 + */ + // public boolean singleUavPowerOff(int fkUavId) { + // String uavId = UavIdMap.getUavId(fkUavId); + // JSONObject body = new JSONObject(); + // body.put("gcsId", GlobalData.GCS_ID); + // body.put("uavId", uavId); + // JSONObject responseJson = JSON.parseObject(CacHpApi.uavPowerOff(body.toString())); + // if (responseJson != null && responseJson.getBoolean("success")) { + // log.info("在控飞机 {} 下电成功", fkUavId); + // return true; + // } else { + // log.info("在控飞机 {} 下电失败: {}", fkUavId, responseJson); + // return false; + // } + // } + + // + // /** + // * 无人机全部下电 + // */ + // public boolean allUavPowerOff() { + // try { + // List fkUavIdList = remoteService.queryGcsControlUav(); + // if (fkUavIdList == null) { + // log.warn("查询在控飞机失败, 无法下线"); + // return false; + // } + // if (fkUavIdList.isEmpty()) { + // log.info("当前无在控飞机"); + // return true; + // } + // // 逐个下电 + // for (int fkUavId : fkUavIdList) { + // singleUavPowerOff(fkUavId); + // } + // + // fkUavIdList = remoteService.queryGcsControlUav(); + // if (fkUavIdList == null) { + // return false; + // } + // if (fkUavIdList.isEmpty()) { + // log.info("地面站所有在控飞机下电成功"); + // return true; + // } else { + // log.warn("以下在控飞机下电失败: {}, 无法下线", fkUavIdList); + // return false; + // } + // } catch (Exception e) { + // e.printStackTrace(); + // return false; + // } + // } + + /** + * 下发无人机控制权 + * @param fkUavId 飞控id + * @param rightState 是否下发控制权 + */ + // public void issuedUavControl(int fkUavId, boolean rightState) { + // ByteBuf bufToGcs = Unpooled.buffer(); + // bufToGcs.writeByte((byte) fkUavId); + // bufToGcs.writeByte((byte) (rightState ? 0x00 : 0x01)); + // ChannelFuture sendFuture = sendToGcs(GcsFrameEnum.CONTROL_ISSUE.getCode(), bufToGcs); + // if (sendFuture!=null && sendFuture.isSuccess() && rightState){ + // uavYgStatusChangeNotify(fkUavId, 0); + // } + // } + +} diff --git a/src/main/java/com/platform/cac/RemoteService.java b/src/main/java/com/platform/cac/RemoteService.java new file mode 100644 index 0000000..9c3036e --- /dev/null +++ b/src/main/java/com/platform/cac/RemoteService.java @@ -0,0 +1,184 @@ +package com.platform.cac; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import com.platform.cac.tcp.CacClient; +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.info.GlobalData; +import com.platform.info.enums.UavTypeEnum; +import com.platform.info.mapping.HaborUavMap; +import com.platform.info.mapping.UavIdMap; +import com.platform.model.DirectControlUavParam; +import com.platform.model.Result; +import com.platform.util.JSONUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.util.List; + + +/** + * @Author : shiyi + * @Date : 2024/1/31 18:54 + * @Description : 中心指控提供的一些信息查询接口 + */ +@Component +@Slf4j +public class RemoteService { + @Resource + CacClient cacClient; + + /** + * 接收到运管通知的消息回报 + */ + public Boolean remoteResponse(RemoteTcpBaseDataFrame data, byte type) { + try { + ByteBuf bufToRemote = Unpooled.buffer(); + bufToRemote.writeShort(data.getMagicCode()); + bufToRemote.writeByte(data.getVersion()); + bufToRemote.writeByte(data.getSerializationAlgorithm()); + bufToRemote.writeByte(type); + bufToRemote.writeByte(data.getGcsIdLength()); + bufToRemote.writeBytes(data.getGcsId().getBytes(StandardCharsets.UTF_8)); + bufToRemote.writeByte(data.getGcsAuthLength()); + bufToRemote.writeBytes(data.getGcsAuth().getBytes(StandardCharsets.UTF_8)); + bufToRemote.writeByte(data.getUavIdLength()); + if (data.getUavId() != null) { + bufToRemote.writeBytes(data.getUavId().getBytes(StandardCharsets.UTF_8)); + } + bufToRemote.writeByte(0x01); + bufToRemote.writeInt(0); + bufToRemote.writeByte(0x00); + cacClient.channel.writeAndFlush(bufToRemote); + return true; + } catch (Exception e) { + log.error("运管报文回复失败", e); + return false; + } + } + + /** + * 根据fkId和无人机类型查询后台uavId,并更新缓存映射关系 + * @param fkUavId 飞控序列号 + * @return uavId 后端的uavId + */ + // public String queryUavId(int fkUavId) { + // JSONObject body = new JSONObject(); + // body.put("flightControlSn", String.valueOf(fkUavId)); + // if (GlobalData.UAV_TYPE == null ) { + // log.error("uav_type未知, 查询无人机ID失败"); + // return null; + // } + // body.put("uavType", GlobalData.UAV_TYPE.getRemoteCode()); + // body.put("gcsId", GCS_ID); + // log.debug("查询无人机id: request body: {}", body); + // + // String uavId = null; + // JSONObject responseJson = JSON.parseObject(CacHpApi.queryUavId(body.toString())); + // if (responseJson!= null && responseJson.containsKey("data")) { + // uavId = responseJson.getString("data"); + // if (uavId == null){ + // log.error("查询无人机ID失败,fkId={}找不到对应的uavId", fkUavId); + // } else { + // UavIdMap.addMap(fkUavId, uavId); + // } + // } else { + // log.error("查询无人机ID请求失败:{}", responseJson); + // } + // + // return uavId; + // } + + /** + * 查询并更新当前地面站在控飞机的无人机映射关系 + */ + public void queryCacDirectControlMapping() { + // todo 每次申请都查一次映射关系 + JSONObject body = new JSONObject(); + body.put("gcsId", GlobalData.GCS_ID); + log.debug("查询全量无人机映射关系,request body: {}", body); + try { + List directControlUavParamList= CacHpApi.queryAllCacDirectControlUavList(body.toString()); + if (CollectionUtils.isEmpty(directControlUavParamList)) { + log.error("查询全量无人机映射关系失败"); + } else { + UavIdMap.clear(); + HaborUavMap.clear(); + directControlUavParamList.forEach(mapping -> { + UavTypeEnum uavType = UavTypeEnum.getByRemoteCode(mapping.getUavType()); + // DONE 2024/6/26: 记录所有型号的飞机映射 + UavIdMap.addMap(uavType, Integer.parseInt(mapping.getFlightControlSn()), mapping.getUavId()); + HaborUavMap.addMap(mapping.getUavId(), mapping.getHarborSn()); + }); + log.info("直控无人机参数映射关系: {}", directControlUavParamList); + } + // if (UAV_TYPE != null) { + // log.info("当前{}型号无人机映射关系(fkId→uavId):{}", UAV_TYPE, UavIdMap.showMap()); + // } else { + // log.info("无法获取当前无人机型号,所有型号无人机映射关系(fkId→uavId)为:{}", UavIdMap.showAllMap()); + // } + } catch (Exception e) { + e.printStackTrace(); + log.error("查询全量无人机映射关系失败"); + } + } + + /** + * 查询地面站在控无人机 + */ + // public List queryGcsControlUav() { + // List uavIdList = queryGcsControlUavByUavId(); + // if (uavIdList == null) { + // return null; + // } + // UavIdControlMap.clear(); + // ArrayList fkUavIdList = new ArrayList<>(); + // for (String uavId : uavIdList) { + // // 如果uavId不属于当前地面站可控型号,则跳过 + // if (UavIdMap.uavIdControllable(uavId)) { + // int fkId = UavIdMap.getFkId(uavId); + // fkUavIdList.add(fkId); + // UavIdControlMap.addMap(fkId, uavId); + // } + // } + // return fkUavIdList; + // } + /** + * 查询地面站在控无人机 + */ + // public List queryGcsControlUavByUavId() { + // JSONObject body = new JSONObject(); + // body.put("gcsId", GCS_ID); + // + // String responseStr = CacHpApi.queryGcsControlUav(body.toString()); + // JSONObject controlUav = CacHpApi.checkResponse(responseStr); + // if (StringUtils.isEmpty(responseStr) || controlUav == null){ + // log.error("在控飞机查询失败, 请检查无人机映射关系:{}", UavIdMap.showMap()); + // return null; + // } + // return controlUav.getObject("uavIdList", ArrayList.class); + // } + + /** + * 查询地面站类型 + */ + // public GcsTypeEnum queryGcsType() { + // JSONObject body = new JSONObject(); + // body.put("gcsId", GCS_ID); + // Result result = JSON.parseObject(CacHpApi.queryGcsTypeByGcsId(body.toString()), Result.class); + // if (result == null || !result.isSuccess()) { + // log.error("查询地面站类型失败!"); + // return null; + // } else { + // Integer gcsType = Integer.valueOf((String) result.getData()); + // return GcsTypeEnum.getTypeByCode(gcsType); + // } + // } + +} diff --git a/src/main/java/com/platform/cac/tcp/CacClient.java b/src/main/java/com/platform/cac/tcp/CacClient.java new file mode 100644 index 0000000..b3da921 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/CacClient.java @@ -0,0 +1,115 @@ +package com.platform.cac.tcp; + + + +import com.platform.cac.GcsService; +import com.platform.cac.tcp.codec.RemoteTcpBaseDataDecoder; +import com.platform.cac.tcp.codec.RemoteTcpBaseDataEncoder; +import com.platform.cac.tcp.message.RemoteMessageDispatcher; +import com.platform.config.CacRemoteConfig; +import com.platform.info.GlobalData; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.ResourceLeakDetector; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +/** + * @Author : shiyi + * @Date : 2023/12/30 16:57 + * @Description : 作为客户端,连接运管和中心指控 + */ +@Slf4j +@Component +public class CacClient { + @Resource + private CacRemoteConfig cacRemoteConfig; + + @Resource + CacConnectionHandler cacConnectionHandler; + @Resource + RemoteMessageDispatcher remoteMessageDispatcher; + static EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); + private Bootstrap bootstrap = new Bootstrap(); + + public Channel channel; + + @Resource + GcsService gcsService; + + public void initClient(EventLoopGroup group) { + + ByteBuf delimiter = Unpooled.buffer(); + delimiter.writeShort(GlobalData.REMOTE_HEAD); + if (null == group) { + group = eventLoopGroup; + } + bootstrap.group(group) + .channel(NioSocketChannel.class) + .remoteAddress(new InetSocketAddress(cacRemoteConfig.getIp(), cacRemoteConfig.getRemoteTcpPort())) + .handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast("ping", new IdleStateHandler(10, 10, 10, TimeUnit.SECONDS)); + ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, true, true, delimiter)); + ch.pipeline().addLast(new RemoteTcpBaseDataDecoder()); + ch.pipeline().addLast(new RemoteTcpBaseDataEncoder()); + ch.pipeline().addLast(cacConnectionHandler); + ch.pipeline().addLast(remoteMessageDispatcher); + } + }); + } + + @PostConstruct + public void start() { + initClient(null); + // ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED); + ChannelFuture future = bootstrap.connect().addListener((ChannelFuture futureListener) -> { + if (!futureListener.isSuccess()) { + log.info("与" + cacRemoteConfig.getIp() + ":" + cacRemoteConfig.getRemoteTcpPort() + "连接失败! 重连中..."); + connect(); + } else { + channel = futureListener.channel(); + log.debug("start-channel: {}", channel); + } + }); + } + /** + * 连接指定的服务端,连接失败自动重连 + */ + public void connect() { + ChannelFuture channelFuture = bootstrap.connect(); + // 使用最新的ChannelFuture -> 开启最新的监听器 + channelFuture.addListener((ChannelFutureListener) future -> { + if (future.cause() != null) { + log.info("与{}:{}连接失败! 重连中...", cacRemoteConfig.getIp(),cacRemoteConfig.getRemoteTcpPort() ); + future.channel().eventLoop().schedule(this::connect, 3, TimeUnit.SECONDS); + } else { + channel = future.channel(); + log.debug("reconnect-channel: {}", channel); + if (GlobalData.GCS_SIGNIN) { + log.info("[reconnect] 与中心指控{}:{}重连成功, 认证中", cacRemoteConfig.getIp(),cacRemoteConfig.getRemoteTcpPort() ); + gcsService.gcsAuthRequestToCtrl(); + } + } + }); + } + + @PreDestroy + private void end() { + eventLoopGroup.shutdownGracefully(); + } +} \ No newline at end of file diff --git a/src/main/java/com/platform/cac/tcp/CacConnectionHandler.java b/src/main/java/com/platform/cac/tcp/CacConnectionHandler.java new file mode 100644 index 0000000..43f87a7 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/CacConnectionHandler.java @@ -0,0 +1,107 @@ +package com.platform.cac.tcp; + + +import com.platform.cac.GcsService; +import com.platform.cac.tcp.message.dataframe.send.TcpHeartBeatRequest; +import com.platform.info.GlobalData; +import com.platform.info.enums.GcsTypeEnum; +import com.platform.info.enums.RemoteFrameEnum; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + + + +/** + * @Author : shiyi + * @Date : 2024/1/3 14:14 + * @Description : 处理连接、断联、重连等逻辑 + */ +@Slf4j +@Component +@ChannelHandler.Sharable +public class CacConnectionHandler extends ChannelInboundHandlerAdapter { + @Resource + CacClient cacClient; + @Resource + GcsService gcsService; + + @Value("${airport.enable:#{false}}") + boolean isAirport = false; + /** + * 建立连接时 + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress(); + int port = ipSocket.getPort(); + String host = ipSocket.getHostString(); + log.info("与中心指控{}:{}建立tcp连接!", host, port); + cacClient.channel = ctx.channel(); + + log.info("地面站发送上线请求"); + if (gcsService.gcsSignInRequest()) { + gcsService.gcsAuthRequestToCtrl(); + } + log.debug("connect-channel: {}", ctx.channel()); + ctx.fireChannelActive(); + } + + /** + * 关闭连接时 + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress(); + int port = ipSocket.getPort(); + String host = ipSocket.getHostString(); + log.error("与中心指控{}:{}连接断开!", host, port); + log.info("断线重连......"); + cacClient.connect(); + } + + + /** + * 心跳机制 + */ + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent) { + IdleState state = ((IdleStateEvent) evt).state(); + if (IdleState.WRITER_IDLE.equals(state)) { + //心跳 + TcpHeartBeatRequest tcpHeartBeatRequest = buildHeatBeatRequest(); + ctx.channel().writeAndFlush(tcpHeartBeatRequest); + } + + } + } + + private TcpHeartBeatRequest buildHeatBeatRequest(){ + TcpHeartBeatRequest tcpHeartBeatRequest = new TcpHeartBeatRequest(); + tcpHeartBeatRequest.setMagicCode(GlobalData.REMOTE_HEAD); + tcpHeartBeatRequest.setVersion(GlobalData.REMOTE_VERSION2); + tcpHeartBeatRequest.setSerializationAlgorithm(GlobalData.REMOTE_SER_ALG); + tcpHeartBeatRequest.setType(RemoteFrameEnum.HEART_BEAT_REQUEST.getCode()); + tcpHeartBeatRequest.setGcsIdLength((byte) GlobalData.GCS_ID.getBytes().length); + tcpHeartBeatRequest.setGcsId(GlobalData.GCS_ID); + tcpHeartBeatRequest.setGcsAuthLength((byte) GlobalData.GCS_TOKEN.getBytes().length); + tcpHeartBeatRequest.setGcsAuth(GlobalData.GCS_TOKEN); + tcpHeartBeatRequest.setUavIdLength((byte) 0); + tcpHeartBeatRequest.setCurrentTime(System.currentTimeMillis()); + byte[] readableDataBytes = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(tcpHeartBeatRequest.getCurrentTime()).array(); + tcpHeartBeatRequest.setReadableDataBytesLength(Long.SIZE / Byte.SIZE); + tcpHeartBeatRequest.setReadableDataBytes(readableDataBytes); + return tcpHeartBeatRequest; + } +} diff --git a/src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataDecoder.java b/src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataDecoder.java new file mode 100644 index 0000000..2cf69e4 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataDecoder.java @@ -0,0 +1,101 @@ +package com.platform.cac.tcp.codec; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.info.GlobalData; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + + +@Slf4j +public class RemoteTcpBaseDataDecoder extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + + + // byte[] buffer = new byte[in.readableBytes()]; + // in.readBytes(buffer); + // in.release(); + // log.info("msg: {}", ByteUtils.bytes2HexString(buffer)); + + RemoteTcpBaseDataFrame remoteTcpBaseDataFrame = new RemoteTcpBaseDataFrame(); + // 标记当前读取位置 + in.markReaderIndex(); + // 判断是否大于最小长度 + if (in.readableBytes() <= 8) { + in.resetReaderIndex(); + log.error("[GcsTcpBaseDataDecoder][连接({}) 解析消息失败,数据可读长度小于数据帧最小长度in={}]", ctx.channel().id(), in.toString()); + in.clear(); + return; + } + + byte[] totalIn = new byte[in.readableBytes()]; + in.readBytes(totalIn); + // if (totalIn[2] != 0x04) { + // log.debug("完整消息: 88 99 {}", ByteUtils.bytes2HexString(totalIn)); + // } + in.resetReaderIndex(); + + remoteTcpBaseDataFrame.setMagicCode(GlobalData.REMOTE_HEAD); + remoteTcpBaseDataFrame.setVersion(in.readByte()); + remoteTcpBaseDataFrame.setSerializationAlgorithm(in.readByte()); + remoteTcpBaseDataFrame.setType(in.readByte()); + // 获取地面站编号长度以及地面站编号 + byte gcsIdLengthByte = in.readByte(); + int gcsIdLength = Byte.toUnsignedInt(gcsIdLengthByte); + if (in.readableBytes() < gcsIdLength) { + in.resetReaderIndex(); + log.error("[GcsTcpBaseDataDecoder][连接({}) 解析消息失败,数据剩余可读长度小于gcsId长度,in={}]", ctx.channel().id(), in.toString()); + return; + } + byte[] gcsIdByteArray = new byte[gcsIdLength]; + in.readBytes(gcsIdByteArray); + remoteTcpBaseDataFrame.setGcsIdLength(gcsIdLengthByte); + remoteTcpBaseDataFrame.setGcsId(new String(gcsIdByteArray)); + // 获取地面站Token长度以及地面站Token + byte gcsTokenLengthByte = in.readByte(); + int gcsTokenLength = Byte.toUnsignedInt(gcsTokenLengthByte); + if (in.readableBytes() < gcsTokenLength) { + in.resetReaderIndex(); + log.error("[GcsTcpBaseDataDecoder][连接({}) 解析消息失败,数据剩余可读长度小于gcsToken长度,in={}]", ctx.channel().id(), in.toString()); + return; + } + byte[] gcsTokenByteArray = new byte[gcsTokenLength]; + in.readBytes(gcsTokenByteArray); + remoteTcpBaseDataFrame.setGcsAuthLength(gcsTokenLengthByte); + remoteTcpBaseDataFrame.setGcsAuth(new String(gcsTokenByteArray)); + + // 获取无人机编号长度以及无人机编号 + byte uavIdLengthByte = in.readByte(); + int uavIdLength = Byte.toUnsignedInt(uavIdLengthByte); + if (in.readableBytes() < uavIdLength) { + in.resetReaderIndex(); + log.error("[GcsTcpBaseDataDecoder][连接({}) 解析消息失败,数据剩余可读长度小于uavId长度,in={}]", ctx.channel().id(), in.toString()); + return; + } + if (uavIdLength == 0) { + remoteTcpBaseDataFrame.setUavIdLength(uavIdLengthByte); + remoteTcpBaseDataFrame.setUavId(null); + } else { + byte[] uavIdByteArray = new byte[uavIdLength]; + in.readBytes(uavIdByteArray); + remoteTcpBaseDataFrame.setUavIdLength(uavIdLengthByte); + remoteTcpBaseDataFrame.setUavId(new String(uavIdByteArray)); + } + // 读取剩余字节 + remoteTcpBaseDataFrame.setReadableDataBytesLength(in.readableBytes()); + if (in.readableBytes() > 0) { + byte[] readableDataBytes = new byte[in.readableBytes()]; + in.readBytes(readableDataBytes); + remoteTcpBaseDataFrame.setReadableDataBytes(readableDataBytes); + } + out.add(remoteTcpBaseDataFrame); + // TODO: 2023/6/14 测试之后记得删除此log + // log.info("[GcsTcpBaseDataDecoder][连接({}) 解析到一条消息({})]", ctx.channel().id(), gcsTcpBaseDataFrame.toString()); + } +} diff --git a/src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataEncoder.java b/src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataEncoder.java new file mode 100644 index 0000000..9fa8bc7 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/codec/RemoteTcpBaseDataEncoder.java @@ -0,0 +1,42 @@ +package com.platform.cac.tcp.codec; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +public class RemoteTcpBaseDataEncoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, RemoteTcpBaseDataFrame dataFrame, ByteBuf buf) { + if (dataFrame != null) { + buf.writeShort(dataFrame.getMagicCode()); + buf.writeByte(dataFrame.getVersion()); + buf.writeByte(dataFrame.getSerializationAlgorithm()); + buf.writeByte(dataFrame.getType()); + buf.writeByte(dataFrame.getGcsIdLength()); + if (dataFrame.getGcsIdLength() > 0) { + buf.writeBytes(dataFrame.getGcsId().getBytes(), 0, dataFrame.getGcsIdLength()); + } + buf.writeByte(dataFrame.getGcsAuthLength()); + if (dataFrame.getGcsAuthLength() > 0) { + buf.writeBytes(dataFrame.getGcsAuth().getBytes(), 0, dataFrame.getGcsAuthLength()); + } + buf.writeByte(dataFrame.getUavIdLength()); + if (dataFrame.getUavIdLength() > 0) { + buf.writeBytes(dataFrame.getUavId().getBytes(), 0, dataFrame.getUavIdLength()); + } + if (dataFrame.getReadableDataBytesLength() > 0) { + buf.writeBytes(dataFrame.getReadableDataBytes(), 0, dataFrame.getReadableDataBytesLength()); + } + // TODO: 2023/6/13 测试之后记得删除此log + // log.debug("[encode]连接({}) 编码了一条消息: {}", ctx.channel().id(), ByteUtils.bytes2HexString(ByteBufUtil.getBytes(buf))); + } + } + +} diff --git a/src/main/java/com/platform/cac/tcp/message/RemoteMessageDispatcher.java b/src/main/java/com/platform/cac/tcp/message/RemoteMessageDispatcher.java new file mode 100644 index 0000000..1f83249 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/RemoteMessageDispatcher.java @@ -0,0 +1,64 @@ +package com.platform.cac.tcp.message; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.cac.tcp.message.handler.IRemoteMessageHandler; +import com.platform.info.enums.RemoteFrameEnum; +import com.platform.util.ByteUtils; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @Author shiyi + * @Date 2023/12/27 + * @Description 根据数据中的帧头和指令类型,获取对应的消息处理器 + */ + +@ChannelHandler.Sharable +@Component +@Slf4j +public class RemoteMessageDispatcher extends SimpleChannelInboundHandler { + + @Autowired + private RemoteMessageHandlerContainer remoteMessageHandlerContainer; + + + @Override + public void channelRead0(ChannelHandlerContext ctx, RemoteTcpBaseDataFrame message) throws InterruptedException { + RemoteFrameEnum messageType = getNettyMessageType(message); + // 获得 type 对应的 MessageHandler 处理器 + IRemoteMessageHandler messageHandler = remoteMessageHandlerContainer.getMessageHandler(messageType); + // 执行逻辑 + if (messageHandler != null) { + log.debug("执行逻辑 {}", messageHandler.getFrameType()); + messageHandler.execute(ctx.channel(), message); + } else { + if (messageType != null) { + ctx.fireChannelRead(message); + } + } + } + + private RemoteFrameEnum getNettyMessageType(RemoteTcpBaseDataFrame message) { + RemoteFrameEnum remoteFrameEnum = RemoteFrameEnum.getByCode(message.getType()); + if (remoteFrameEnum == null) { + log.warn("未知的远程消息类型: {}, 无对应处理逻辑", ByteUtils.byteToHex(message.getType())); + return null; + } + if (remoteFrameEnum.equals(RemoteFrameEnum.HEART_BEAT_RESPONSE) || remoteFrameEnum.equals(RemoteFrameEnum.HEART_BEAT_REQUEST)) { + // 过滤心跳信息 + return null; + } + return remoteFrameEnum; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("exceptionCaught: {}", cause.getMessage(), cause); + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/RemoteMessageHandlerContainer.java b/src/main/java/com/platform/cac/tcp/message/RemoteMessageHandlerContainer.java new file mode 100644 index 0000000..f16ba16 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/RemoteMessageHandlerContainer.java @@ -0,0 +1,47 @@ +package com.platform.cac.tcp.message; + + + +import com.platform.cac.tcp.message.handler.IRemoteMessageHandler; +import com.platform.info.enums.RemoteFrameEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Slf4j +@Component +public class RemoteMessageHandlerContainer implements InitializingBean { + + /** + * 消息类型与 IGcsMessageHandler 的映射 + */ + private final Map handlers = new HashMap<>(); + @Autowired + private ApplicationContext applicationContext; + + @Override + public void afterPropertiesSet() throws Exception { + // 通过 ApplicationContext 获得所有 MessageHandler Bean + applicationContext.getBeansOfType(IRemoteMessageHandler.class).values() + .forEach(messageHandler -> handlers.put(messageHandler.getFrameType(), messageHandler)); + log.info("[afterPropertiesSet] IRemoteMessageHandler 数量:{}", handlers.size()); + } + + /** + * 获得类型对应的 IMessageHandler + */ + public IRemoteMessageHandler getMessageHandler(RemoteFrameEnum remoteFrameEnum) { + return handlers.get(remoteFrameEnum); + } + +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/RemoteTcpBaseDataFrame.java b/src/main/java/com/platform/cac/tcp/message/dataframe/RemoteTcpBaseDataFrame.java new file mode 100644 index 0000000..66f7cf7 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/RemoteTcpBaseDataFrame.java @@ -0,0 +1,41 @@ +package com.platform.cac.tcp.message.dataframe; + +import lombok.Data; + + +@Data +public class RemoteTcpBaseDataFrame { + + private short magicCode; + private byte version; + private byte serializationAlgorithm; + private byte type; + private byte gcsIdLength; + private String gcsId; + private byte gcsAuthLength; + private String gcsAuth; + private byte uavIdLength; + private String uavId; + private int readableDataBytesLength; + private byte[] readableDataBytes; + + public RemoteTcpBaseDataFrame() { + } + + /** + * 属性复制 + */ + public RemoteTcpBaseDataFrame(RemoteTcpBaseDataFrame data) { + // 将data的基础属性复制过来 + this.magicCode = data.getMagicCode(); + this.version = data.getVersion(); + this.serializationAlgorithm = data.getSerializationAlgorithm(); + this.type = data.getType(); + this.gcsIdLength = data.getGcsIdLength(); + this.gcsId = data.getGcsId(); + this.gcsAuthLength = data.getGcsAuthLength(); + this.gcsAuth = data.getGcsAuth(); + this.uavIdLength = data.getUavIdLength(); + this.uavId = data.getUavId(); + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReceiveUavControlMessage.java b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReceiveUavControlMessage.java new file mode 100644 index 0000000..54f5d7a --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReceiveUavControlMessage.java @@ -0,0 +1,42 @@ +package com.platform.cac.tcp.message.dataframe.receive; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.util.ByteUtils; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + +@Getter +@ToString +@Slf4j +public class CacReceiveUavControlMessage extends RemoteTcpBaseDataFrame { + private byte receiveGcsIdLength; + private String receiveGcsId; + private byte uavControlAccept; + private byte uavControlAppplyIdLength; + private String uavControlApplyId; + + public CacReceiveUavControlMessage(RemoteTcpBaseDataFrame data) { + super(data); + + ByteBuffer buffer = ByteBuffer.allocate(data.getReadableDataBytesLength()).put(data.getReadableDataBytes()); + buffer.flip(); + try { + receiveGcsIdLength = buffer.get(); + receiveGcsId = ByteUtils.getString(buffer, receiveGcsIdLength); + + uavControlAccept = buffer.get(); + + uavControlAppplyIdLength = buffer.get(); + uavControlApplyId = ByteUtils.getString(buffer, uavControlAppplyIdLength); + } catch (Exception e){ + log.error("中心指控-控制权接收数据解析异常"); + e.printStackTrace(); + } + + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReplyUavControlMessage.java b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReplyUavControlMessage.java new file mode 100644 index 0000000..3810272 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacReplyUavControlMessage.java @@ -0,0 +1,46 @@ +package com.platform.cac.tcp.message.dataframe.receive; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.util.ByteUtils; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + + +@Getter +@ToString +@Slf4j +public class CacReplyUavControlMessage extends RemoteTcpBaseDataFrame { + + private byte replyGcsIdLength; + private String replyGcsId; + private byte uavControlApprove; + private byte uavControlApplyIdLength; + private String uavControlApplyId; + + public CacReplyUavControlMessage(RemoteTcpBaseDataFrame data) { + super(data); + + ByteBuffer buffer = ByteBuffer.allocate(data.getReadableDataBytesLength()).put(data.getReadableDataBytes()); + buffer.flip(); + + try { + replyGcsIdLength = buffer.get(); + replyGcsId = ByteUtils.getString(buffer, replyGcsIdLength); + + uavControlApprove = buffer.get(); + + uavControlApplyIdLength = buffer.get(); + uavControlApplyId = ByteUtils.getString(buffer, uavControlApplyIdLength); + } catch (Exception e) { + log.error("中心指控-控制权回复数据解析异常"); + e.printStackTrace(); + } + + } + +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavCommandMessage.java b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavCommandMessage.java new file mode 100644 index 0000000..13f1096 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavCommandMessage.java @@ -0,0 +1,41 @@ +package com.platform.cac.tcp.message.dataframe.receive; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.util.ByteUtils; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + + + +@Slf4j +@Getter +@ToString +public class CacUavCommandMessage extends RemoteTcpBaseDataFrame { + + private byte uavCommandCodeIdLength; + private byte[] uavCommandCodeId; + private byte uavControlUniIdLength; + private String uavControlUniId; + + public CacUavCommandMessage(RemoteTcpBaseDataFrame data){ + super(data); + ByteBuffer buffer = ByteBuffer.allocate(data.getReadableDataBytesLength()).put(data.getReadableDataBytes()); + buffer.flip(); + + try { + uavCommandCodeIdLength = buffer.get(); + uavCommandCodeId = new byte[uavCommandCodeIdLength]; + buffer.get(uavCommandCodeId); + uavControlUniIdLength=buffer.get(); + uavControlUniId = ByteUtils.getString(buffer, uavControlUniIdLength); + } catch (Exception e) { + log.error("中心指控-指令数据解析异常"); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavControlApplyMessage.java b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavControlApplyMessage.java new file mode 100644 index 0000000..57a76af --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/CacUavControlApplyMessage.java @@ -0,0 +1,42 @@ +package com.platform.cac.tcp.message.dataframe.receive; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.util.ByteUtils; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + +@Getter +@Slf4j +public class CacUavControlApplyMessage extends RemoteTcpBaseDataFrame { + + private byte applyGcsIdLength; + private String applyGcsId; + private byte uavControlApplyReasonLength; + private String uavControlApplyReason; + private byte uavControlApplyIdLength; + private String uavControlApplyId; + + public CacUavControlApplyMessage(RemoteTcpBaseDataFrame data){ + super(data); + ByteBuffer buffer = ByteBuffer.allocate(data.getReadableDataBytesLength()).put(data.getReadableDataBytes()); + buffer.flip(); + try { + applyGcsIdLength = buffer.get(); + applyGcsId = ByteUtils.getString(buffer, applyGcsIdLength); + + uavControlApplyReasonLength = buffer.get(); + uavControlApplyReason = ByteUtils.getString(buffer, uavControlApplyReasonLength); + + uavControlApplyIdLength = buffer.get(); + uavControlApplyId = ByteUtils.getString(buffer, uavControlApplyIdLength); + } catch (Exception e){ + log.error("中心指控-控制权申请数据解析异常"); + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/receive/TcpRemoteAuthorization.java b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/TcpRemoteAuthorization.java new file mode 100644 index 0000000..76d5731 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/receive/TcpRemoteAuthorization.java @@ -0,0 +1,37 @@ +package com.platform.cac.tcp.message.dataframe.receive; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.util.ByteUtils; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + + +@Data +@Slf4j +public class TcpRemoteAuthorization extends RemoteTcpBaseDataFrame { + + private byte success; + private int code; + private byte messageLength; + private String message; + + public TcpRemoteAuthorization(RemoteTcpBaseDataFrame data) { + super(data); + ByteBuffer buffer = ByteBuffer.allocate(data.getReadableDataBytesLength()).put(data.getReadableDataBytes()); + buffer.flip(); + + try { + success = buffer.get(); + code = buffer.getInt(); + + messageLength = buffer.get(); + message = ByteUtils.getString(buffer, messageLength); + } catch (Exception e) { + log.error("中心指控-地面站认证数据解析异常"); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanReplyRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanReplyRequest.java new file mode 100644 index 0000000..07d7019 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanReplyRequest.java @@ -0,0 +1,21 @@ +package com.platform.cac.tcp.message.dataframe.send; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Data +public class TcpFlightPlanReplyRequest extends RemoteTcpBaseDataFrame { + + private byte applyFlightPlanIdLength; + private String applyFlightPlanId; + private byte replyFlightPlanIdLength; + private String replyFlightPlanId; + private byte flightPlanPass; + +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanRevokeRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanRevokeRequest.java new file mode 100644 index 0000000..6aae0cb --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlightPlanRevokeRequest.java @@ -0,0 +1,20 @@ +package com.platform.cac.tcp.message.dataframe.send; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/28 + * @Description 描述 + */ +@Data +public class TcpFlightPlanRevokeRequest extends RemoteTcpBaseDataFrame { + + private byte applyFlightPlanIdLength; + private String applyFlightPlanId; + private byte revokeReasonLength; + private String revokeReason; + +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlyReplyRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlyReplyRequest.java new file mode 100644 index 0000000..a6fb84f --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpFlyReplyRequest.java @@ -0,0 +1,21 @@ +package com.platform.cac.tcp.message.dataframe.send; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Data +public class TcpFlyReplyRequest extends RemoteTcpBaseDataFrame { + + private byte applyFlyIdLength; + private String applyFlyId; + private byte replyFlyIdLength; + private String replyFlyId; + private byte flyPass; + +} \ No newline at end of file diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpHeartBeatRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpHeartBeatRequest.java new file mode 100644 index 0000000..2f5f6d9 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpHeartBeatRequest.java @@ -0,0 +1,17 @@ +package com.platform.cac.tcp.message.dataframe.send; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Data +public class TcpHeartBeatRequest extends RemoteTcpBaseDataFrame { + + long currentTime; +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAlarmRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAlarmRequest.java new file mode 100644 index 0000000..db0654c --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAlarmRequest.java @@ -0,0 +1,19 @@ +package com.platform.cac.tcp.message.dataframe.send; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Data +public class TcpReceiveAlarmRequest extends RemoteTcpBaseDataFrame { + + private int alarmLevel; + private byte contentLength; + private String content; + private long effectTime; +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAtcRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAtcRequest.java new file mode 100644 index 0000000..9afad06 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpReceiveAtcRequest.java @@ -0,0 +1,18 @@ +package com.platform.cac.tcp.message.dataframe.send; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Data +public class TcpReceiveAtcRequest extends RemoteTcpBaseDataFrame { + + private int atcType; + private long effectTime; + +} diff --git a/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpRemoteAuthCacRequest.java b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpRemoteAuthCacRequest.java new file mode 100644 index 0000000..47f74d1 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/dataframe/send/TcpRemoteAuthCacRequest.java @@ -0,0 +1,14 @@ +package com.platform.cac.tcp.message.dataframe.send; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import lombok.Data; + +/** + * @Author sunjipeng + * @Date 2023/6/14 + * @Description 描述 + */ +@Data +public class TcpRemoteAuthCacRequest extends RemoteTcpBaseDataFrame { +} diff --git a/src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandIssuedHandler.java b/src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandIssuedHandler.java new file mode 100644 index 0000000..0228ee6 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandIssuedHandler.java @@ -0,0 +1,58 @@ +package com.platform.cac.tcp.message.handler; + + +import com.platform.cac.GcsService; +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.cac.tcp.message.dataframe.receive.CacUavCommandMessage; +import com.platform.info.enums.GcsFrameEnum; +import com.platform.info.enums.RemoteFrameEnum; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @Author : shiyi + * @Date : 2024/1/9 11:28 + * @Description : 远程中心指控指令下发 + */ +@Component @Slf4j +public class CacUavCommandIssuedHandler implements IRemoteMessageHandler { + @Resource + GcsService gcsService; + + @Override + public void execute(Channel channel, RemoteTcpBaseDataFrame remoteTcpBaseDataFrame) { + if (gcsService.isSignIn()){ + CacUavCommandMessage uavCommand = new CacUavCommandMessage(remoteTcpBaseDataFrame); + log.info("对地面站下发指令...{}", uavCommand); + // GcsService.commandUniId = uavCommand.getUavControlUniId(); + // log.debug("commandUniId Info: {}", GcsService.commandUniId); + // issuedUavCommandToGcs(uavCommand); + } else { + log.warn("无法下发指令"); + } + } + /** + * 下发指令到地面站 + * @param uavCommand + */ + // private void issuedUavCommandToGcs(CacUavCommandMessage uavCommand) { + // String uavId = uavCommand.getUavId(); + // int fkUavId = UavIdMap.getFkId(uavId); + // + // ByteBuf bufToGcs = Unpooled.buffer(); + // bufToGcs.writeByte((byte) fkUavId); + // bufToGcs.writeByte(uavCommand.getUavCommandCodeIdLength()); + // bufToGcs.writeBytes(uavCommand.getUavCommandCodeId()); + // gcsService.sendToGcs(GcsFrameEnum.ISSUED_COMMAND.getCode(), bufToGcs); + // } + + @Override + public RemoteFrameEnum getFrameType() { + return RemoteFrameEnum.UAV_COMMAND_ISSUED; + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandResultHandler.java b/src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandResultHandler.java new file mode 100644 index 0000000..7f2257b --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/handler/CacUavCommandResultHandler.java @@ -0,0 +1,57 @@ +package com.platform.cac.tcp.message.handler; + + +import com.platform.cac.GcsService; +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.cac.tcp.message.dataframe.receive.CacUavCommandMessage; +import com.platform.info.enums.GcsFrameEnum; +import com.platform.info.enums.RemoteFrameEnum; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @Author : shiyi + * @Date : 2024/1/24 17:38 + * @Description : 查询指令是否完成 + */ +@Component +@Slf4j +public class CacUavCommandResultHandler implements IRemoteMessageHandler { + @Resource + GcsService gcsService; + @Override + public void execute(Channel channel, RemoteTcpBaseDataFrame remoteTcpBaseDataFrame) { + if (gcsService.isSignIn()){ + CacUavCommandMessage uavCommand = new CacUavCommandMessage(remoteTcpBaseDataFrame); + log.debug("向地面站查询指令...{}", uavCommand); + + // GcsService.queryCommandUniId= uavCommand.getUavControlUniId(); + // log.debug("queryCommandUniId Info: {}", GcsService.queryCommandUniId); + queryUavCommandResult(uavCommand); + } else { + log.warn("无法查询指令"); + } + } + /** + * 向地面站查询指令 + * @param uavCommand + */ + private void queryUavCommandResult(CacUavCommandMessage uavCommand) { + // String uavId = uavCommand.getUavId(); + // int fkUavId = UavIdMap.getFkId(uavId); + // ByteBuf bufToGcs = Unpooled.buffer(); + // bufToGcs.writeByte((byte) fkUavId); + // bufToGcs.writeByte(uavCommand.getUavCommandCodeIdLength()); + // bufToGcs.writeBytes(uavCommand.getUavCommandCodeId()); + // gcsService.sendToGcs(GcsFrameEnum.QUERY_COMMAND.getCode(), bufToGcs); + } + @Override + public RemoteFrameEnum getFrameType() { + return RemoteFrameEnum.UAV_COMMAND_QUERY; + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/handler/CacUavControlApplyHandler.java b/src/main/java/com/platform/cac/tcp/message/handler/CacUavControlApplyHandler.java new file mode 100644 index 0000000..a7f6932 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/handler/CacUavControlApplyHandler.java @@ -0,0 +1,60 @@ +package com.platform.cac.tcp.message.handler; + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.cac.tcp.message.dataframe.receive.CacUavControlApplyMessage; +import com.platform.info.enums.GcsFrameEnum; +import com.platform.info.enums.RemoteFrameEnum; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @Author : shiyi + * @Date : 2024/1/9 10:28 + * @Description : 收到远程的控制权申请请求,并转发给地面站 + */ +@Component @Slf4j +public class CacUavControlApplyHandler implements IRemoteMessageHandler { + // @Resource + // GcsService gcsService; + + @Override + public void execute(Channel channel, RemoteTcpBaseDataFrame remoteTcpBaseDataFrame) { + CacUavControlApplyMessage cacUavControlApplyMessage = new CacUavControlApplyMessage(remoteTcpBaseDataFrame); + applyUavControlToGcs(cacUavControlApplyMessage); + } + + + /** + * 收到指控传来的控制权申请,并向地面站申请释放控制权。 + * 地面站的回复: {@link com.htfp.gcsplugin.netty.server.messagehandler.handler.GcsUavControlReply} + * + * @param cacUavControlApplyMessage + */ + private void applyUavControlToGcs(CacUavControlApplyMessage cacUavControlApplyMessage) { + // byte reason = (byte) Integer.parseInt(cacUavControlApplyMessage.getUavControlApplyReason()); + // String uavId = cacUavControlApplyMessage.getUavId(); + // int fkUavId = UavIdMap.getFkId(uavId); + // byte applyGcsId = (byte) Integer.parseInt(cacUavControlApplyMessage.getApplyGcsId()); + // String applyId = cacUavControlApplyMessage.getUavControlApplyId(); + // GcsService.applyIdToProcess.push(applyGcsId +"-" + uavId, applyId); + // log.info("收到来自地面站 {} 的对无人机 {} 控制请求, 请求原因: {}, applyId: {}", applyGcsId, fkUavId, reason, applyId); + // log.debug("applyId info: {}", GcsService.applyIdToProcess.toString()); + // + // // 回复地面站 + // ByteBuf bufToGcs = Unpooled.buffer(); + // bufToGcs.writeByte(applyGcsId); + // bufToGcs.writeByte((byte) fkUavId); + // bufToGcs.writeByte(reason); + // gcsService.sendToGcs(GcsFrameEnum.CONTROL_FREE.getCode(), bufToGcs); + } + @Override + public RemoteFrameEnum getFrameType() { + return RemoteFrameEnum.UAV_CONTROL_APPLY; + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/handler/GcsAuthResponseHandler.java b/src/main/java/com/platform/cac/tcp/message/handler/GcsAuthResponseHandler.java new file mode 100644 index 0000000..dcc8fd6 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/handler/GcsAuthResponseHandler.java @@ -0,0 +1,46 @@ +package com.platform.cac.tcp.message.handler; + + +import com.platform.cac.GcsService; +import com.platform.cac.RemoteService; +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.cac.tcp.message.dataframe.receive.TcpRemoteAuthorization; +import com.platform.info.GlobalData; +import com.platform.info.enums.RemoteFrameEnum; +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + + +/** + * @Author : shiyi + * @Date : 2024/1/3 14:11 + * @Description : 地面站接入校验的回复 + */ +@Component @Slf4j +public class GcsAuthResponseHandler implements IRemoteMessageHandler { + + @Resource + RemoteService remoteService; + @Resource + GcsService gcsService; + @Override + public void execute(Channel channel, RemoteTcpBaseDataFrame remoteTcpBaseDataFrame) { + TcpRemoteAuthorization authResponse = new TcpRemoteAuthorization(remoteTcpBaseDataFrame); + + GlobalData.GCS_SIGNIN = (0x01==authResponse.getSuccess()); + if (GlobalData.GCS_SIGNIN){ + remoteService.queryCacDirectControlMapping(); // 同步操作 + // List controlFkUavList = remoteService.queryGcsControlUav(); + // log.info("当前在控无人机: {}", remoteService.queryGcsControlUav()); + } + log.info("[tcp auth]认证上线{}, code={}, message: {}", GlobalData.GCS_SIGNIN ? "成功":"失败",authResponse.getCode(), authResponse.getMessage()); + } + + @Override + public RemoteFrameEnum getFrameType() { + return RemoteFrameEnum.GCS_AUTH_CAC_RESPONSE; + } +} diff --git a/src/main/java/com/platform/cac/tcp/message/handler/IRemoteMessageHandler.java b/src/main/java/com/platform/cac/tcp/message/handler/IRemoteMessageHandler.java new file mode 100644 index 0000000..f80b2f7 --- /dev/null +++ b/src/main/java/com/platform/cac/tcp/message/handler/IRemoteMessageHandler.java @@ -0,0 +1,25 @@ +package com.platform.cac.tcp.message.handler; + + + +import com.platform.cac.tcp.message.dataframe.RemoteTcpBaseDataFrame; +import com.platform.info.enums.RemoteFrameEnum; +import io.netty.channel.Channel; + + +public interface IRemoteMessageHandler { + /** + * 执行处理消息 + * + * @param channel 通道 + * @param gcsTcpBaseDataFrame 消息 + */ + void execute(Channel channel, RemoteTcpBaseDataFrame remoteTcpBaseDataFrame); + + /** + * @return 消息类型 + */ + RemoteFrameEnum getFrameType(); + + +} diff --git a/src/main/java/com/platform/config/CacRemoteConfig.java b/src/main/java/com/platform/config/CacRemoteConfig.java new file mode 100644 index 0000000..9e9e272 --- /dev/null +++ b/src/main/java/com/platform/config/CacRemoteConfig.java @@ -0,0 +1,15 @@ +package com.platform.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "remote-cac") +public class CacRemoteConfig { + public String ip; + public int remoteTcpPort; + public String udpIp; + public int remoteUdpPort; +} \ No newline at end of file diff --git a/src/main/java/com/platform/config/ServiceConfig.java b/src/main/java/com/platform/config/ServiceConfig.java index 59bc685..0d981a7 100644 --- a/src/main/java/com/platform/config/ServiceConfig.java +++ b/src/main/java/com/platform/config/ServiceConfig.java @@ -7,7 +7,7 @@ import org.springframework.context.annotation.Configuration; @Configuration @Data public class ServiceConfig { - @Value("${log.debug}") + @Value("${app.debug}") private boolean debug; @Value("${service.port}") private Integer port; diff --git a/src/main/java/com/platform/controller/ClientController.java b/src/main/java/com/platform/controller/ClientController.java index a4bc2c4..439038a 100644 --- a/src/main/java/com/platform/controller/ClientController.java +++ b/src/main/java/com/platform/controller/ClientController.java @@ -15,7 +15,9 @@ import org.springframework.web.bind.annotation.*; import java.util.*; import java.util.stream.Collectors; - +/** + * 哈勃服务器 地面站-哈勃映射配置接口 + */ @RestController @Slf4j public class ClientController { diff --git a/src/main/java/com/platform/info/GlobalData.java b/src/main/java/com/platform/info/GlobalData.java new file mode 100644 index 0000000..52c62f3 --- /dev/null +++ b/src/main/java/com/platform/info/GlobalData.java @@ -0,0 +1,40 @@ +package com.platform.info; + +import com.platform.info.enums.UavTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/** + * @Author : shiyi + * @Date : 2024/1/2 13:57 + * @Description : 全局变量信息 + */ +@Configuration @Slf4j +public class GlobalData { + @Autowired + public void init(@Value("${cac_gcs.id}") String id, @Value("${cac_gcs.token}") String token) { + GlobalData.GCS_ID = id; + GlobalData.GCS_TOKEN = token; + log.info("中心指控系统-地面站ID: {}", GlobalData.GCS_ID); + } + + + public static String GCS_ID; + // 地面站上线请求发送之前使用token + public static String GCS_TOKEN; + + public static final short REMOTE_HEAD = (short) 0x8899; + public static final byte REMOTE_VERSION1 = 0x01; // 遥测数据中的版本号, 发给运管,对应type=0x0F + public static final byte REMOTE_VERSION2 = 0x02; // 遥测数据中的版本号,发给中心指控, 对应type=0x04 + public static final byte REMOTE_SER_ALG = 0x00;// 序列化算法 + public static UavTypeEnum UAV_TYPE; + + public static boolean GCS_LOGIN = false; + public static boolean GCS_SIGNIN = false; + public static String AUTHORIZATION = ""; + public static String commandUniId; + public static String queryCommandUniId; + +} diff --git a/src/main/java/com/platform/info/enums/ClientTypeEnum.java b/src/main/java/com/platform/info/enums/ClientTypeEnum.java new file mode 100644 index 0000000..55c4d21 --- /dev/null +++ b/src/main/java/com/platform/info/enums/ClientTypeEnum.java @@ -0,0 +1,37 @@ +package com.platform.info.enums; + +import lombok.Getter; + +/** + * @Author : shiyi + * @Date : 2024/1/3 9:28 + * @Description : 帧头枚举 + */ +@Getter +public enum ClientTypeEnum { + // + GCS((short) 0xCC06, (short) 0xCC06,"地面站"), + AUTO_AIRPORT((short) 0xCC07, (short) 0xCC07,"自动化机场"), + ; + private final short readHead; + private final short writeHead; + private final String info; + + ClientTypeEnum(short readHead, short writeHead, String info) { + + this.readHead = readHead; + this.writeHead = writeHead; + this.info = info; + } + + + public static ClientTypeEnum getByHead(int head) { + for (ClientTypeEnum clientTypeEnum : ClientTypeEnum.values()) { + if (clientTypeEnum.getReadHead() == head) { + return clientTypeEnum; + } + } + return null; + } + +} diff --git a/src/main/java/com/platform/info/enums/GcsFrameEnum.java b/src/main/java/com/platform/info/enums/GcsFrameEnum.java new file mode 100644 index 0000000..e2b3038 --- /dev/null +++ b/src/main/java/com/platform/info/enums/GcsFrameEnum.java @@ -0,0 +1,65 @@ +package com.platform.info.enums; + +/** + * @Author : shiyi + * @Date : 2024/1/2 13:46 + * @Description : 地面站帧类型枚举 + */ + + +public enum GcsFrameEnum implements IClientFrameEnum { + // 与地面站之间TCP传输的报文类型枚举 + + LOGIN((byte) 0x00, "接入校验"), + TELEMETRY_DATA_TRANSFER((byte) 0x02, "遥测数据透传"), + HEART_BEAT((byte) 0x03, "心跳"), + CONTROL_APPLY((byte) 0x04, "控制权申请,运管在控无人机更新"), + CONTROL_FREE((byte) 0x0A, "控制权释放"), + CONTROL_ISSUE((byte) 0x0B, "控制权下发"), + CONTROL_RECEIVE((byte) 0x0C, "控制权接收"), + ISSUED_COMMAND((byte) 0x05, "通知指令下发"), + RECONNECT((byte) 0x06, "地面站重连"), + SNAPSHOT((byte) 0x07, "发送地面站快照"), + SIGN_IN((byte) 0x08, "连接中心指控"), + SIGN_OUT((byte) 0x09, "断开中心指控"), + COMMAND_NOTIFY((byte) 0x0D, "遥控指令回报"), + UPDATE_UAV_CONTROL((byte) 0x0E, "中心指控在控无人机更新"), + QUERY_COMMAND((byte) 0x20, "查询指令是否完成"), + UAV_MASTER_CONTROL((byte) 0x21, "地面站主控通知"), + UPDATE_REMOTE_CONTROL_STATE((byte) 0x22, "远程控制状态"), + QUERY_REMOTE_CONTROL_STATE_REPLY((byte) 0x23, "查询远程控制状态"), + STREAM_PUSH((byte) 0x32, "视频流推送"), + ; + + private final byte code; + private final String info; + + GcsFrameEnum(byte code, String info) { + this.code = code; + this.info = info; + } + + public byte getCode() { + return code; + } + + public String getInfo() { + return info; + } + + public static GcsFrameEnum getByCode(byte frameCode) { + for (GcsFrameEnum enums : GcsFrameEnum.values()) { + if (enums.code == frameCode) { + return enums; + } + } + return null; + } + + @Override + public ClientTypeEnum getClientEnum() { + return ClientTypeEnum.GCS; + } +} + + diff --git a/src/main/java/com/platform/info/enums/GcsTypeEnum.java b/src/main/java/com/platform/info/enums/GcsTypeEnum.java new file mode 100644 index 0000000..0bdf022 --- /dev/null +++ b/src/main/java/com/platform/info/enums/GcsTypeEnum.java @@ -0,0 +1,41 @@ +package com.platform.info.enums; + +/** + * @Author : shiyi + * @Date : 2024/2/17 16:36 + * @Description : 地面站类型枚举 + */ +public enum GcsTypeEnum { + // 地面站类型枚举 + REMOTE_GCS(0, "远程地面站"), + BOX_GCS(1, "箱式地面站"), + HANDHELD_GCS(2, "手持地面站"), + CARBIN_GCS(3, "方舱地面站"), + AIRPORT_GCS(4,"机场型地面站"), + ; + + private final int code; + private final String info; + + GcsTypeEnum(int code, String info) { + this.code = code; + this.info = info; + } + + public int getCode() { + return code; + } + + public String getInfo() { + return info; + } + + public static GcsTypeEnum getTypeByCode(Integer code) { + for (GcsTypeEnum enums : GcsTypeEnum.values()) { + if (enums.getCode() == code) { + return enums; + } + } + return null; + } +} diff --git a/src/main/java/com/platform/info/enums/IClientFrameEnum.java b/src/main/java/com/platform/info/enums/IClientFrameEnum.java new file mode 100644 index 0000000..8f6cb8d --- /dev/null +++ b/src/main/java/com/platform/info/enums/IClientFrameEnum.java @@ -0,0 +1,10 @@ +package com.platform.info.enums; + +/** + * @Author : shiyi + * @Date : 2023/12/28 12:02 + * @Description : + */ +public interface IClientFrameEnum { + public ClientTypeEnum getClientEnum(); +} diff --git a/src/main/java/com/platform/info/enums/RemoteFrameEnum.java b/src/main/java/com/platform/info/enums/RemoteFrameEnum.java new file mode 100644 index 0000000..11f7666 --- /dev/null +++ b/src/main/java/com/platform/info/enums/RemoteFrameEnum.java @@ -0,0 +1,71 @@ +package com.platform.info.enums; + +/** + * @Author : shiyi + * @Date : 2024/1/2 13:46 + * @Description : 运管和中心指控帧类型枚举 + */ + +public enum RemoteFrameEnum { + // 运管 + GCS_AUTH_CAC_REQUEST((byte) 0x01, "地面站接入数据路由认证请求"), + GCS_AUTH_CAC_RESPONSE((byte)0x02, "地面站接入数据路由认证响应"), + HEART_BEAT_REQUEST((byte) 0x03, "心跳请求"), + HEART_BEAT_RESPONSE((byte) 0x04, "心跳响应"), + FLIGHT_PLAN_REPLY_REQUEST((byte) 0x05, "飞行计划审批结果通知"), + FLIGHT_PLAN_REPLY_RESPONSE((byte) 0x06, "飞行计划审批结果响应"), + FLY_REPLY_REQUEST((byte) 0x07, "放飞申请结果通知"), + FLY_REPLY_RESPONSE((byte) 0x08, "放飞申请结果响应"), + RECEIVE_ALARM_REQUEST((byte) 0x09, "告警信息"), + RECEIVE_ALARM_RESPONSE((byte) 0x0A, "告警信息接收响应"), + RECEIVE_ATC_REQUEST( (byte) 0x0B, "管制指令通知"), + RECEIVE_ATC_RESPONSE((byte) 0x0C, "管制指令接收响应"), + FLIGHT_PLAN_REVOKE_REQUEST((byte) 0x0D, "飞行计划撤销通知"), + FLIGHT_PLAN_REVOKE_RESPONSE((byte) 0x0E, "飞行计划撤销响应"), + UAV_TELEMETRY_DATA_TRANSFER((byte) 0x0F, "无人机遥测数据透传"), + AIRSPACE_REPLY_REQUEST((byte) 0x1A, "空域申请结果通知"), + AIRSPACE_REPLY_RESPONSE((byte) 0x1B, "空域申请结果收到响应"), + // 中心指控 + UAV_CONTROL_APPLY((byte) 0x10, "申请无人机控制权"), + UAV_CONTROL_REPLY((byte) 0x11, "回复无人机控制权"), + UAV_CONTROL_RECEIVE((byte) 0x12, "接收无人机控制权"), + UAV_COMMAND_ISSUED((byte) 0x13, "无人机控制指令下发"), + UAV_COMMAND_QUERY((byte) 0x14, "无人机控制指令查询"), + + // 自动化机场 + AIRPORT_COMMAND_ISSUED((byte) 0x15, "机场指令下发"), + AIRPORT_COMMAND_QUERY((byte) 0x16, "机场指令查询"), + AIRPORT_STATUS_QUERY((byte) 0x17, "机场状态查询"), + AIRPORT_STATE_OF_CHARGE_QUERY((byte)0x18, "电格电量查询"), + AIRPORT_CONTAINER_STATUS_QUERY((byte)0x19, "货格状态查询"), + + // 推流地址 + STREAM_PUSH((byte) 0x21, "推流地址"); + + ; + private final byte code; + private final String info; + public byte getCode() { + return code; + } + + public String getInfo() { + return info; + } + + + RemoteFrameEnum(byte code, String info) { + this.code = code; + this.info = info; + } + + public static RemoteFrameEnum getByCode(byte frameCode) { + for (RemoteFrameEnum enums : RemoteFrameEnum.values()) { + if (enums.code == frameCode) { + return enums; + } + } + return null; + } + +} diff --git a/src/main/java/com/platform/info/enums/UavTypeEnum.java b/src/main/java/com/platform/info/enums/UavTypeEnum.java new file mode 100644 index 0000000..97fc20f --- /dev/null +++ b/src/main/java/com/platform/info/enums/UavTypeEnum.java @@ -0,0 +1,67 @@ +package com.platform.info.enums; + +import lombok.Getter; + +/** + * @Author : shiyi + * @Date : 2024/1/9 9:27 + * @Description : 无人机类型枚举 + */ +@Getter +public enum UavTypeEnum { + // + FP981A (0x01, 1,"981A", false), + FP981C (0x02, 2,"981C", false), + FP98 (0x04, 4,"98", true), + FP985 (0x08, 8,"985", true), + FP981CS (0x10, 16,"981CS", true), + FH981CH (0x20, 32,"981CH", true), + // 此后的编码和中心指控编码区分开 + FH981CH_HYBRID (0x21, 64,"981CH-二代机混动", false), + FH981CH_ELECTRIC (0x22, 128,"981CH-二代机电动", false), + ; + private final int gcsCode; + private final int remoteCode; + private final String info; + private final boolean onlyMonitor; // 仅具有监视功能,但不控制 + + UavTypeEnum(int code, int remoteCode, String info, boolean onlyMonitor) { + this.gcsCode = code; + this.remoteCode = remoteCode; + this.info = info; + this.onlyMonitor = onlyMonitor; + } + + public int getGcsCode() { + return gcsCode; + } + public int getRemoteCode() { + return remoteCode; + } + public String getInfo() { + return info; + } + public boolean isOnlyMonitor() { + return onlyMonitor; + } + + public String toString() { + return this.info; + } + public static UavTypeEnum getByGcsCode(int code) { + for (UavTypeEnum typeEnum : UavTypeEnum.values()) { + if (typeEnum.getGcsCode() == code) { + return typeEnum; + } + } + return null; + } + public static UavTypeEnum getByRemoteCode(int code) { + for (UavTypeEnum typeEnum : UavTypeEnum.values()) { + if (typeEnum.getRemoteCode() == code) { + return typeEnum; + } + } + return null; + } +} diff --git a/src/main/java/com/platform/info/mapping/HaborUavMap.java b/src/main/java/com/platform/info/mapping/HaborUavMap.java new file mode 100644 index 0000000..55e922a --- /dev/null +++ b/src/main/java/com/platform/info/mapping/HaborUavMap.java @@ -0,0 +1,55 @@ +package com.platform.info.mapping; + + + +import javax.validation.constraints.NotNull; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * 中心指控系统 哈勃Sn和uavId之间的映射 + */ + +public class HaborUavMap { + // 所有型号,uavId到fkId的映射 + private static final ConcurrentHashMap habor2uavId = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavId2habor = new ConcurrentHashMap<>(); + // /** 添加当前型号的映射关系*/ + // public static void addMap(Integer fkId, String uavId){ + // addMap(UAV_TYPE, fkId, uavId); + // } + + /**根据飞机型号增加映射关系*/ + public static void addMap(String uavId, String harboSn){ + habor2uavId.put(harboSn, uavId); + uavId2habor.put(uavId, harboSn); + } + + /** 获取指定型号的映射关系*/ + public static String showMap(){ + return "haboroSn→uavId: " + habor2uavId; + } + + public static String getHaborSnByUavId(@NotNull String uavId){ + return uavId2habor.get(uavId); + } + public static String getUavIdByHaborSn(@NotNull String haborSn){ + return habor2uavId.get(haborSn); + } + + + + /** 当前地面站是否可控哈勃 */ + public static boolean haborIsControllable(@NotNull String haborSn){ + return habor2uavId.contains(haborSn) && uavId2habor.containsValue(haborSn); + } + + /** 当前地面站是否可控uavId*/ + public static boolean uavIsControllable(@NotNull String uavId){ + return uavId2habor.contains(uavId) && habor2uavId.containsValue(uavId); + } + public static void clear() { + habor2uavId.clear(); + uavId2habor.clear(); + } +} diff --git a/src/main/java/com/platform/info/mapping/UavIdControlMap.java b/src/main/java/com/platform/info/mapping/UavIdControlMap.java new file mode 100644 index 0000000..23cc228 --- /dev/null +++ b/src/main/java/com/platform/info/mapping/UavIdControlMap.java @@ -0,0 +1,100 @@ +package com.platform.info.mapping; + + + +import com.platform.info.enums.UavTypeEnum; + +import java.util.concurrent.ConcurrentHashMap; + +/**当前在控无人机的缓存表,每次查询在控无人机的时候将更新该表 + * Note: 该列表不保证和后端同步,如需准确获知在控情况请直接调用查询接口 + */ + +public class UavIdControlMap { + // 所有型号,uavId到fkId的映射 + private static final ConcurrentHashMap globalMapId = new ConcurrentHashMap<>(); + + // 各个型号,fkId到uavId的映射 + private static final ConcurrentHashMap uavMapId981A = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId981C = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId98 = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId985 = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId981CS = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> uavTypeMapId = new ConcurrentHashMap<>(); + + + static { + uavTypeMapId.put(UavTypeEnum.FP981A, uavMapId981A); + uavTypeMapId.put(UavTypeEnum.FP981C, uavMapId981C); + uavTypeMapId.put(UavTypeEnum.FP98, uavMapId98); + uavTypeMapId.put(UavTypeEnum.FP985, uavMapId985); + uavTypeMapId.put(UavTypeEnum.FP981CS, uavMapId981CS); + } + // private static BidiMap uavMapId = new DualHashBidiMap<>(); + + + /**根据飞机型号增加映射关系*/ + public static void addMap(UavTypeEnum uavType, Integer fkId, String uavId){ + globalMapId.put(uavId, fkId); + if (uavType != null && uavTypeMapId.containsKey(uavType)) { + uavTypeMapId.get(uavType).put(fkId, uavId); + } + } + + + /** 获取指定型号的映射关系*/ + public static String showMap(UavTypeEnum uavType){ + if (uavType != null && uavTypeMapId.containsKey(uavType)) { + return uavTypeMapId.get(uavType).toString(); + } + return null; + } + + /** + * 获取所有型号的映射关系 + */ + public static String showAllMap(){ + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + uavTypeMapId.forEach((uavType, uavIdMap) -> { + sb.append(uavType.getInfo()).append(":").append(uavTypeMapId.get(uavType)).append("\n"); + }); + sb.append("}"); + return sb.toString(); + } + + + + /**根据指定型号的fkId获取uavId*/ + public static String getUavId(UavTypeEnum uavType, Integer fkId){ + ConcurrentHashMap uavMapId = uavTypeMapId.get(uavType); + if (fkId ==null || uavMapId == null|| !uavMapId.containsKey(fkId)){ + throw new IllegalArgumentException(uavType.getInfo() + "类型fkId=" + fkId + " 无法转换为uavId, 该类型映射关系: " + uavMapId); + } + return uavMapId.get(fkId); + } + + + /** + * 根据uavId获取飞控id + */ + public static int getFkId(String uavId){ + if (uavId ==null || !globalMapId.containsKey(uavId)){ + throw new IllegalArgumentException("uavId=" + uavId + " not found in uavMapId: " + globalMapId); + } + return globalMapId.get(uavId); + } + + /** uavId是否是当前地面站可控 */ + public static boolean uavIdControlling(UavTypeEnum uavType, String uavId){ + return uavTypeMapId.get(uavType).containsValue(uavId); + } + + /** fkId是否是当前地面站可控 */ + public static boolean fkIdControlling(UavTypeEnum uavType, Integer fkId){ + return uavTypeMapId.get(uavType).containsKey(fkId); + } + public static void clear() { + uavTypeMapId.forEach((uavType, uavIdMap) -> uavIdMap.clear()); + } +} diff --git a/src/main/java/com/platform/info/mapping/UavIdMap.java b/src/main/java/com/platform/info/mapping/UavIdMap.java new file mode 100644 index 0000000..332c0a6 --- /dev/null +++ b/src/main/java/com/platform/info/mapping/UavIdMap.java @@ -0,0 +1,113 @@ +package com.platform.info.mapping; + + + +import com.platform.info.enums.UavTypeEnum; + +import java.util.concurrent.ConcurrentHashMap; + + + +/** + * fkId和uavId之间的映射 + * 注意,不同型号的fkId可能重复,但是uavId不会重复 + * 将fkId转换为uavId的时候,需要根据当前uav_type进行判断 + */ + +public class UavIdMap { + // 所有型号,uavId到fkId的映射 + private static final ConcurrentHashMap globalMapId = new ConcurrentHashMap<>(); + + // 各个型号,fkId到uavId的映射 + private static final ConcurrentHashMap uavMapId981A = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId981C = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId98 = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId985 = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId981CS = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap uavMapId981CH = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> uavTypeMapId = new ConcurrentHashMap<>(); + + + static { + uavTypeMapId.put(UavTypeEnum.FP981A, uavMapId981A); + uavTypeMapId.put(UavTypeEnum.FP981C, uavMapId981C); + uavTypeMapId.put(UavTypeEnum.FP98, uavMapId98); + uavTypeMapId.put(UavTypeEnum.FP985, uavMapId985); + uavTypeMapId.put(UavTypeEnum.FP981CS, uavMapId981CS); + uavTypeMapId.put(UavTypeEnum.FH981CH, uavMapId981CH); + } + // private static BidiMap uavMapId = new DualHashBidiMap<>(); + + // /** 添加当前型号的映射关系*/ + // public static void addMap(Integer fkId, String uavId){ + // addMap(UAV_TYPE, fkId, uavId); + // } + + /**根据飞机型号增加映射关系*/ + public static void addMap(UavTypeEnum uavType, Integer fkId, String uavId){ + globalMapId.put(uavId, fkId); + if (uavType != null && uavTypeMapId.containsKey(uavType)) { + uavTypeMapId.get(uavType).put(fkId, uavId); + } + } + + // /** 获取当前型号的映射关系*/ + // public static String showMap(){ + // return showMap(UAV_TYPE); + // } + + /** 获取指定型号的映射关系*/ + public static String showMap(UavTypeEnum uavType){ + if (uavType != null && uavTypeMapId.containsKey(uavType)) { + return uavTypeMapId.get(uavType).toString(); + } + return null; + } + + /** + * 获取所有型号的映射关系 + */ + public static String showAllMap(){ + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + uavTypeMapId.forEach((uavType, uavIdMap) -> { + sb.append(uavType.getInfo()).append(":").append(uavTypeMapId.get(uavType)).append("\n"); + }); + sb.append("}"); + return sb.toString(); + } + + + /**根据指定型号的fkId获取uavId*/ + public static String getUavId(UavTypeEnum uavType, Integer fkId){ + ConcurrentHashMap uavMapId = uavTypeMapId.get(uavType); + if (fkId ==null || uavMapId == null|| !uavMapId.containsKey(fkId)){ + throw new IllegalArgumentException(uavType.getInfo() + "类型fkId=" + fkId + " 无法转换为uavId, 该类型映射关系: " + uavMapId); + } + return uavMapId.get(fkId); + } + + + /** + * 根据uavId获取飞控id + */ + public static int getFkId(String uavId){ + if (uavId ==null || !globalMapId.containsKey(uavId)){ + throw new IllegalArgumentException("uavId=" + uavId + " not found in uavMapId: " + globalMapId); + } + return globalMapId.get(uavId); + } + + /** uavId是否是当前地面站可控 */ + public static boolean uavIdControllable(UavTypeEnum uavType, String uavId){ + return uavTypeMapId.get(uavType).containsValue(uavId); + } + + /** fkId是否是当前地面站可控 */ + public static boolean fkIdControllable(UavTypeEnum uavType, Integer fkId){ + return uavTypeMapId.get(uavType).containsKey(fkId); + } + public static void clear() { + uavTypeMapId.forEach((uavType, uavIdMap) -> uavIdMap.clear()); + } +} diff --git a/src/main/java/com/platform/model/DirectControlUavParam.java b/src/main/java/com/platform/model/DirectControlUavParam.java new file mode 100644 index 0000000..516690d --- /dev/null +++ b/src/main/java/com/platform/model/DirectControlUavParam.java @@ -0,0 +1,11 @@ +package com.platform.model; + +import lombok.Data; + +@Data +public class DirectControlUavParam { + String harborSn; + String flightControlSn; + String uavId; + Integer uavType; +} diff --git a/src/main/java/com/platform/model/Result.java b/src/main/java/com/platform/model/Result.java new file mode 100644 index 0000000..8a26baa --- /dev/null +++ b/src/main/java/com/platform/model/Result.java @@ -0,0 +1,17 @@ +package com.platform.model; + +import lombok.Data; +import lombok.ToString; + +/** + * @Author : shiyi + * @Date : 2024/2/18 9:16 + * @Description : HTTP统一响应结果 + */ +@Data +public class Result { + boolean success; + String message; + Integer code; + T data; +} diff --git a/src/main/java/com/platform/service/InMessageHandler.java b/src/main/java/com/platform/service/InMessageHandler.java index 97da41a..5093f90 100644 --- a/src/main/java/com/platform/service/InMessageHandler.java +++ b/src/main/java/com/platform/service/InMessageHandler.java @@ -77,7 +77,7 @@ public class InMessageHandler extends ChannelInboundHandlerAdapter { public void channelRead(ChannelHandlerContext ctx, Object msg0) throws Exception { String address = getAddress(ctx); - byte[] msg = (byte[])msg0; + byte[] msg = (byte[])msg0; // TODO 2025/1/16: Byte fkID = msg[4]; diff --git a/src/main/java/com/platform/service/ServerService.java b/src/main/java/com/platform/service/ServerService.java index 8902893..e17e20a 100644 --- a/src/main/java/com/platform/service/ServerService.java +++ b/src/main/java/com/platform/service/ServerService.java @@ -43,7 +43,7 @@ public class ServerService { bootstrap.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { - channel.pipeline().addLast(new ByteArrayDecoder()); + channel.pipeline().addLast(new ByteArrayDecoder()); // revise by shiyi: 解码移动到InMessageHandler中 channel.pipeline().addLast(new ByteArrayEncoder()); channel.pipeline().addLast(new IdleStateHandler(0, 0, 60 * 24, TimeUnit.MINUTES)); diff --git a/src/main/java/com/platform/util/BaseAuthorizationUtils.java b/src/main/java/com/platform/util/BaseAuthorizationUtils.java new file mode 100644 index 0000000..0bcf963 --- /dev/null +++ b/src/main/java/com/platform/util/BaseAuthorizationUtils.java @@ -0,0 +1,50 @@ +package com.platform.util; + +import org.apache.tomcat.util.codec.binary.Base64; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +public class BaseAuthorizationUtils { + + public static void generateAuthAndDateHeader(Map header, String method, String gcsId, String token, String uri) { + Date sysdate = new Date(); + SimpleDateFormat df = new SimpleDateFormat("EEE\', \'dd\' \'MMM\' \'yyyy\' \'HH:mm:ss\' \'z", Locale.US); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + String date = df.format(sysdate); + header.put("authorization", generateAuth(method, date, gcsId, token, uri)); + header.put("gcsId", gcsId); + header.put("date", date); + } + + public static String generateAuth(String method, String date, String gcsId, String token, String uri){ + String string_to_sign = method.toUpperCase() + " " + uri + "\n" + date; + String sig = token; + String encoding = ""; + + try { + byte[] signature = getSignature(string_to_sign.getBytes(), sig.getBytes()); + encoding = new String(Base64.encodeBase64(signature)); + } catch (Exception var10) { + return null; + } + + return gcsId + ":" + encoding; + } + + public static byte[] getSignature(byte[] data, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException { + SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(signingKey); + byte[] rawHmac = mac.doFinal(data); + return rawHmac; + } + +} \ No newline at end of file diff --git a/src/main/java/com/platform/util/ByteUtils.java b/src/main/java/com/platform/util/ByteUtils.java new file mode 100644 index 0000000..551479b --- /dev/null +++ b/src/main/java/com/platform/util/ByteUtils.java @@ -0,0 +1,543 @@ +package com.platform.util; + +import java.io.*; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +public class ByteUtils { + + /** + * 用来把mac字符串转换为long + * + * @param strMac + * @return + */ + public static long macToLong(String strMac) { + byte[] mb = new BigInteger(strMac, 16).toByteArray(); + ByteBuffer mD = ByteBuffer.allocate(mb.length); + mD.put(mb); + long mac = 0; + // 如果长度等于8代表没有补0; + if (mD.array().length == 8) { + mac = mD.getLong(0); + } else if (mD.array().length == 9) { + mac = mD.getLong(1); + } + return mac; + } + + public static byte[] getBytes(Object obj) throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bout); + out.writeObject(obj); + out.flush(); + byte[] bytes = bout.toByteArray(); + bout.close(); + out.close(); + + return bytes; + } + + /** + * 函数名称:hexStr2Byte
功能描述:String 转数组 + * + * @param hex + */ + public static byte[] hexStr2Byte(String hex) { + + ByteBuffer bf = ByteBuffer.allocate(hex.length() / 2); + for (int i = 0; i < hex.length(); i++) { + String hexStr = hex.charAt(i) + ""; + i++; + hexStr += hex.charAt(i); + byte b = (byte) Integer.parseInt(hexStr, 16); + bf.put(b); + } + return bf.array(); + } + + /** + * 函数名称:byteToHex
功能描述:byte转16进制 + * + * @param b + */ + public static String byteToHex(byte b) { + String hex = Integer.toHexString(b & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + + } + return hex.toUpperCase(Locale.getDefault()); + + } + + public static Object getObject(byte[] bytes) throws IOException, + ClassNotFoundException { + ByteArrayInputStream bi = new ByteArrayInputStream(bytes); + ObjectInputStream oi = new ObjectInputStream(bi); + Object obj = oi.readObject(); + bi.close(); + oi.close(); + return obj; + } + + public static ByteBuffer getByteBuffer(Object obj) throws IOException { + byte[] bytes = ByteUtils.getBytes(obj); + ByteBuffer buff = ByteBuffer.wrap(bytes); + + return buff; + } + + /** + * byte[] 转short 2字节 + * + * @param bytes + * @return + */ + public static short bytesToshort(byte[] bytes) { + return (short) ((bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00)); + + } + + /** + * byte 转Int + * + * @param b + * @return + */ + public static int byteToInt(byte b) { + return (b) & 0xff; + } + + + /** + * @方法功能 字节数组和整型的转换 + * @param 字节数组 + * @return 整型 + */ + public static int bytesToInt(byte[] bytes,int offset) { + int num = bytes[offset] & 0xFF; + num |= ((bytes[offset+1] << 8) & 0xFF00); + num |= ((bytes[offset+2] << 16) & 0xFF0000); + num |= ((bytes[offset+3] << 24) & 0xFF000000); + return num; + } + + public static byte[] intToByte(int i) { + + byte[] abyte0 = new byte[4]; + abyte0[0] = (byte) (0xff & i); + abyte0[1] = (byte) ((0xff00 & i) >> 8); + abyte0[2] = (byte) ((0xff0000 & i) >> 16); + abyte0[3] = (byte) ((0xff000000 & i) >> 24); + return abyte0; + + } + + public static byte[] LongToByte(Long i) { + + byte[] abyte0 = new byte[8]; + abyte0[0] = (byte) (0xff & i); + abyte0[1] = (byte) ((0xff00 & i) >> 8); + abyte0[2] = (byte) ((0xff0000 & i) >> 16); + abyte0[3] = (byte) ((0xff000000 & i) >> 24); + abyte0[4] = (byte) ((0xff00000000l & i) >> 32); + abyte0[5] = (byte) ((0xff0000000000l & i) >> 40); + abyte0[6] = (byte) ((0xff000000000000l & i) >> 48); + abyte0[7] = (byte) ((0xff00000000000000l & i) >> 56); + return abyte0; + + } + + /** + * 函数名称:shortChange
功能描述:short 大端转小端 + * + * @param mshort + */ + public static short shortChange(Short mshort) { + + mshort = (short) ((mshort >> 8 & 0xFF) | (mshort << 8 & 0xFF00)); + + return mshort; + } + + /** + * 函数名称:intChange
功能描述:int 大端转小端 + * + * @param mint + */ + public static int intChange(int mint) { + + mint = (int) (((mint) >> 24 & 0xFF) | ((mint) >> 8 & 0xFF00) + | ((mint) << 8 & 0xFF0000) | ((mint) << 24 & 0xFF000000)); + + return mint; + } + + /** + * 函数名称:intChange
功能描述:LONG 大端转小端 + * + * @param mint + */ + public static long longChange(long mint) { + + mint = (long) (((mint) >> 56 & 0xFF) | ((mint) >> 48 & 0xFF00) + | ((mint) >> 24 & 0xFF0000) | ((mint) >> 8 & 0xFF000000) + | ((mint) << 8 & 0xFF00000000l) + | ((mint) << 24 & 0xFF0000000000l) + | ((mint) << 40 & 0xFF000000000000l) | ((mint) << 56 & 0xFF00000000000000l)); + + return mint; + } + + /** + * 将byte转换为无符号的short类型 + * + * @param b + * 需要转换的字节数 + * @return 转换完成的short + */ + public static short byteToUshort(byte b) { + return (short) (b & 0x00ff); + } + + /** + * 将byte转换为无符号的int类型 + * + * @param b + * 需要转换的字节数 + * @return 转换完成的int + */ + public static int byteToUint(byte b) { + return b & 0x00ff; + } + + /** + * 将byte转换为无符号的long类型 + * + * @param b + * 需要转换的字节数 + * @return 转换完成的long + */ + public static long byteToUlong(byte b) { + return b & 0x00ff; + } + + /** + * 将short转换为无符号的int类型 + * + * @param s + * 需要转换的short + * @return 转换完成的int + */ + public static int shortToUint(short s) { + return s & 0x00ffff; + } + + /** + * 将short转换为无符号的long类型 + * + * @param s + * 需要转换的字节数 + * @return 转换完成的long + */ + public static long shortToUlong(short s) { + return s & 0x00ffff; + } + + /** + * 将int转换为无符号的long类型 + * + * @param i + * 需要转换的字节数 + * @return 转换完成的long + */ + public static long intToUlong(int i) { + return i & 0x00ffffffff; + } + + /** + * 将short转换成小端序的byte数组 + * + * @param s + * 需要转换的short + * @return 转换完成的byte数组 + */ + public static byte[] shortToLittleEndianByteArray(short s) { + return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN) + .putShort(s).array(); + } + + /** + * 将int转换成小端序的byte数组 + * + * @param i + * 需要转换的int + * @return 转换完成的byte数组 + */ + public static byte[] intToLittleEndianByteArray(int i) { + return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(i) + .array(); + } + + /** + * 将long转换成小端序的byte数组 + * + * @param l + * 需要转换的long + * @return 转换完成的byte数组 + */ + public static byte[] longToLittleEndianByteArray(long l) { + return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(l) + .array(); + } + + /** + * 将short转换成大端序的byte数组 + * + * @param s + * 需要转换的short + * @return 转换完成的byte数组 + */ + public static byte[] shortToBigEndianByteArray(short s) { + return ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putShort(s) + .array(); + } + + /** + * 将int转换成大端序的byte数组 + * + * @param i + * 需要转换的int + * @return 转换完成的byte数组 + */ + public static byte[] intToBigEndianByteArray(int i) { + return ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putInt(i) + .array(); + } + + /** + * 将long转换成大端序的byte数组 + * + * @param l + * 需要转换的long + * @return 转换完成的byte数组 + */ + public static byte[] longToBigEndianByteArray(long l) { + return ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putLong(l) + .array(); + } + + /** + * 将short转换为16进制字符串 + * + * @param s + * 需要转换的short + * @param isLittleEndian + * 是否是小端序(true为小端序false为大端序) + * @return 转换后的字符串 + */ + public static String shortToHexString(short s, boolean isLittleEndian) { + byte byteArray[] = null; + if (isLittleEndian) { + byteArray = shortToLittleEndianByteArray(s); + } else { + byteArray = shortToBigEndianByteArray(s); + } + return byteArrayToHexString(byteArray); + } + + /** + * 将int转换为16进制字符串 + * + * @param i + * 需要转换的int + * @param isLittleEndian + * 是否是小端序(true为小端序false为大端序) + * @return 转换后的字符串 + */ + public static String intToHexString(int i, boolean isLittleEndian) { + byte byteArray[] = null; + if (isLittleEndian) { + byteArray = intToLittleEndianByteArray(i); + } else { + byteArray = intToBigEndianByteArray(i); + } + return byteArrayToHexString(byteArray); + } + + /** + * 将long转换为16进制字符串 + * + * @param l + * 需要转换的long + * @param isLittleEndian + * 是否是小端序(true为小端序false为大端序) + * @return 转换后的字符串 + */ + public static String longToHexString(long l, boolean isLittleEndian) { + byte byteArray[] = null; + if (isLittleEndian) { + byteArray = longToLittleEndianByteArray(l); + } else { + byteArray = longToBigEndianByteArray(l); + } + return byteArrayToHexString(byteArray); + } + + /** + * 将字节数组转换成16进制字符串 + * + * @param array + * 需要转换的字符串 + * @param toPrint + * 是否为了打印输出,如果为true则会每4自己添加一个空格 + * @return 转换完成的字符串 + */ + public static String byteArrayToHexString(byte[] array, boolean toPrint) { + if (array == null) { + return "null"; + } + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < array.length; i++) { + sb.append(byteToHex(array[i])); + if (toPrint && (i + 1) % 4 == 0) { + sb.append(" "); + } + } + return sb.toString(); + } + + /** + * 将字节数组转换成16进制字符串 + * + * @param array + * 需要转换的字符串(字节间没有分隔符) + * @return 转换完成的字符串 + */ + public static String byteArrayToHexString(byte[] array) { + return byteArrayToHexString(array, false); + } + + + public static String bytes2HexString(byte[] b) { + String ret = ""; + for (int i = 0; i < b.length; i++) { + String hex = Integer.toHexString(b[ i ] & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + ret += hex.toUpperCase()+" "; + } + return ret; + } + + /** + * 将字节数组转换成long类型 + * + * @param bytes + * 字节数据 + * @return long类型 + */ + public static long byteArrayToLong(byte[] bytes) { + return ((((long) bytes[0] & 0xff) << 24) + | (((long) bytes[1] & 0xff) << 16) + | (((long) bytes[2] & 0xff) << 8) | (((long) bytes[3] & 0xff) << 0)); + } + + + + public static byte[] getCRC(int[] ptr){ + int[] crc_16_table = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 + }; + int crc = 0x0000; + int da = 0x00; + for (int j : ptr) { + da = (crc & 0xff00) >> 8; + crc = (crc & 0x00ff) << 8; + int a = da ^ j; + int b = crc_16_table[a]; + crc ^=b; + } + + + return intToLittleEndianByteArray(crc); + } + + + + + /** + * 计算CRC16校验码 + * + * @param bytes + * @return + */ + public static byte[] getRtuCRC(int[] bytes) { + int CRC = 0x0000ffff; + int POLYNOMIAL = 0x0000a001; + + int i, j; + for (i = 0; i < bytes.length; i++) { + CRC ^= (bytes[i] & 0x000000ff); + for (j = 0; j < 8; j++) { + if ((CRC & 0x00000001) != 0) { + CRC >>= 1; + CRC ^= POLYNOMIAL; + } else { + CRC >>= 1; + } + } + } +// CRC = ( (CRC & 0x0000FF00) >> 8) | ( (CRC & 0x000000FF ) << 8); + return intToLittleEndianByteArray(CRC); + } + + /** + * 从ByteBuffer读取指定长度的字符串 + * @param buffer + * @param length + * @return + */ + public static String getString(ByteBuffer buffer, int length){ + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/com/platform/util/HttpClientUtils.java b/src/main/java/com/platform/util/HttpClientUtils.java new file mode 100644 index 0000000..ec2f4a2 --- /dev/null +++ b/src/main/java/com/platform/util/HttpClientUtils.java @@ -0,0 +1,147 @@ +package com.platform.util; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import okhttp3.Request.Builder; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @Author : shiyi + * @Date : 2024/1/23 16:19 + * @Description : HttpClient工具类 + */ +@Slf4j +public class HttpClientUtils { + public static final String MEDIA_TYPE_JSON = "application/json; charset=utf-8"; + private static final OkHttpClient OKHTTP_CLIENT = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .retryOnConnectionFailure(true) + .build(); + private static final OkHttpClient DONLOAD_OKHTTP_CLIENT = new OkHttpClient.Builder() + .build(); + // public static T sendGet(String url, Map params, Class responseClass) { + // MultiValueMap queryParams = new LinkedMultiValueMap<>(); + // if (params != null) { + // params.forEach(queryParams::add); + // } + // Mono mono = WebClient.create(url).method(HttpMethod.GET) + // .uri(uriBuilder -> uriBuilder + // .queryParams(queryParams) + // .build()).retrieve().bodyToMono(responseClass); + // T responseBody; + // try { + // responseBody = mono.block(); + // } catch (WebClientResponseException e) { + // log.error("Get 请求错误, {}", e.getMessage()); + // return null; + // } + // return responseBody; + // } + public static T sendGet(String url, Map params, Class responseClass) throws Exception { + String jsonStr = sendGet(url, params); + return JSONUtils.json2obj(jsonStr, responseClass); + } + public static String sendGet(String url, Map params) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + if (!CollectionUtils.isEmpty(params)) { + for (String res : params.keySet()) { + if (res == null || params.get(res) == null) { + continue; + } + if (StringUtils.isNotBlank(stringBuilder)) { + stringBuilder.append("&"); + } else { + stringBuilder.append("?"); + } + try { + stringBuilder.append(String.format("%s=%s", res, URLEncoder.encode(params.get(res), "UTF-8"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + } + Request request = new Builder() + .url(url + stringBuilder) + .build(); + + try (Response response = OKHTTP_CLIENT.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("okhttp Unexpected code " + response); + } + ResponseBody body = response.body(); + if (body != null) { + return body.string(); + } else { + return null; + } + } + } + + public static T sendPost(String url, String json, Map headers, Class responseClass) throws Exception { + String jsonStr = sendPost(url, json, headers); + return JSONUtils.json2obj(jsonStr, responseClass); + } + + public static T sendPost(String url, String json, Class responseClass) throws Exception { + String jsonStr = sendPost(url, json, (Map) null); + return JSONUtils.json2obj(jsonStr, responseClass); + } + + public static String sendPost(String url, String json, Map headers) throws IOException { + RequestBody requestBody = RequestBody.create(MediaType.parse(MEDIA_TYPE_JSON), json); + Builder requestBuilder = new Builder() + .url(url) + .post(requestBody); + // 添加请求头 + if (!CollectionUtils.isEmpty(headers)) { + for (Map.Entry entry : headers.entrySet()) { + requestBuilder.addHeader(entry.getKey(), entry.getValue()); + } + } + + Request request = requestBuilder.build(); + try (Response response = OKHTTP_CLIENT.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("okhttp Unexpected code " + response); + } + ResponseBody body = response.body(); + if (body != null) { + return body.string(); + } else { + return null; + } + } + } + public static void downloadFileByUrl(String sourceUrl, String localPath) throws IOException { + File file = new File(localPath); + Request request = new Request.Builder() + .url(sourceUrl) + .build(); + + try (Response response = DONLOAD_OKHTTP_CLIENT.newCall(request).execute()) { + if (response.code() == 200) { + assert response.body() != null; + InputStream stream = response.body().byteStream(); + long dataBytes = Files.copy(stream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + log.debug("文件{}下载完成,文件大小: {} bytes", localPath, dataBytes); + } else { + throw new RuntimeException("文件下载失败,Response:" + response); + } + } + + } + +} diff --git a/src/main/java/com/platform/util/JSONUtils.java b/src/main/java/com/platform/util/JSONUtils.java new file mode 100644 index 0000000..ea844d7 --- /dev/null +++ b/src/main/java/com/platform/util/JSONUtils.java @@ -0,0 +1,100 @@ +package com.platform.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.platform.model.Result; +import io.swagger.models.auth.In; +import lombok.Data; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class JSONUtils { + + private final static ObjectMapper objectMapper = new ObjectMapper(); + + private JSONUtils() { + + } + + public static ObjectMapper getInstance() { + + return objectMapper; + } + + /** + * javaBean,list,array convert to json string + */ + public static String obj2json(Object obj) throws Exception { + return objectMapper.writeValueAsString(obj); + } + + /** + * json string convert to javaBean + */ + public static T json2obj(String jsonStr, Class clazz) + throws Exception { + return objectMapper.readValue(jsonStr, clazz); + } + /** + * 通用静态方法:将JSON字符串转换为带泛型的类 T + */ + public static T json2obj(String json, Class baseClass, Class genericClass) throws IOException { + // 构造目标类型的 JavaType + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(baseClass, genericClass); + // 使用 ObjectMapper 转换 JSON 字符串为目标类型 + return objectMapper.readValue(json, javaType); + } + /** + * json string convert to map + */ + public static Map json2map(String jsonStr) + throws Exception { + return objectMapper.readValue(jsonStr, Map.class); + } + + /** + * json string convert to map with javaBean + */ + public static Map json2map(String jsonStr, Class clazz) + throws Exception { + Map> map = (Map>) objectMapper.readValue(jsonStr, + new TypeReference>() { + }); + Map result = new HashMap(); + for (Map.Entry> entry : map.entrySet()) { + result.put(entry.getKey(), map2obj(entry.getValue(), clazz)); + } + return result; + } + + /** + * json array string convert to list with javaBean + */ + public static List json2list(String jsonArrayStr, Class clazz) + throws Exception { + List> list = (List>) objectMapper.readValue(jsonArrayStr, + new TypeReference>() { + }); + List result = new ArrayList(); + for (Map map : list) { + result.add(map2obj(map, clazz)); + } + return result; + } + + /** + * map convert to javaBean + */ + public static T map2obj(Map map, Class clazz) { + return objectMapper.convertValue(map, clazz); + } + + public static void main(String[] args) throws Exception { + + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 1182436..1c7da17 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,5 +2,33 @@ server: port: 11727 service: port: 11728 -log: - debug: false \ No newline at end of file +app: + debug: false + + + +# 中心指控系统 tcp udp 配置 +remote-cac: + ip: 123.57.54.1 + remoteTcpPort: 4567 + + +# 中心指控系统 http 配置 +http-cac: + host: 123.57.54.1:8048 + + +# 中心指控系统-地面站配置 +cac_gcs: + id: 18 + token: SFRGUEJYR0NTMjAyNTAxMTQ= + + +logging: + level: + com.com.platform: debug +management: + endpoints: + web: + exposure: + include: 'loggers' diff --git a/src/main/resources/log4j2.yml b/src/main/resources/log4j2.yml deleted file mode 100644 index db741ab..0000000 --- a/src/main/resources/log4j2.yml +++ /dev/null @@ -1,49 +0,0 @@ -Configuration: - status: warn - - Properties: # 定义全局变量 - Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下: - #测试:-Dlog.level.console=warn -Dlog.level.xjj=trace - #生产:-Dlog.level.console=warn -Dlog.level.xjj=info - - name: log.level.console - value: trace - - name: log.path - value: ../logs # - - name: project.name - value: netty-all - Appenders: - Console: #输出到控制台 - name: CONSOLE - target: SYSTEM_OUT - PatternLayout: - #pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n" - pattern: "%d [%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-B3-ParentSpanId},%X{X-Span-Export}] %-5p %c:%L [%t] - %m%n" - RollingFile: - - name: ROLLING_FILE - ignoreExceptions: false - fileName: ${log.path}/${project.name}.log - filePattern: "${log.path}/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log" - PatternLayout: - pattern: "%d [%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-B3-ParentSpanId},%X{X-Span-Export}] %-5p %c:%L [%t] - %m%n" - Policies: - TimeBasedTriggeringPolicy: - modulate: true - interval: 1 - SizeBasedTriggeringPolicy: - size: "50M" - DefaultRolloverStrategy: - max: 100 - - Loggers: - Root: - level: info - AppenderRef: - - ref: CONSOLE - - ref: ROLLING_FILE - Logger: - - name: com.platform - additivity: false - level: debug - AppenderRef: - - ref: CONSOLE - - ref: ROLLING_FILE \ No newline at end of file