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