diff --git a/api/Cloud API Demo.postman_collection.json b/api/Cloud API Demo.postman_collection.json index 9c96972..445ea40 100644 --- a/api/Cloud API Demo.postman_collection.json +++ b/api/Cloud API Demo.postman_collection.json @@ -226,6 +226,34 @@ }, "response": [] }, + { + "name": "Switch Lens", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"video_id\": \"1581F5BMD228Q00A82XX/39-0-7/zoom-0\",\r\n \"video_type\": \"zoom\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{manage_version}}/live/streams/switch", + "host": [ + "{{base_url}}{{manage_version}}" + ], + "path": [ + "live", + "streams", + "switch" + ] + } + }, + "response": [] + }, { "name": "Get All Users Info", "request": { @@ -703,6 +731,36 @@ } }, "response": [] + }, + { + "name": "Set Property", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"night_lights_state\": 0\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{manage_version}}/devices/{{workspace_id}}/devices/{{device_sn}}/property", + "host": [ + "{{base_url}}{{manage_version}}" + ], + "path": [ + "devices", + "{{workspace_id}}", + "devices", + "{{device_sn}}", + "property" + ] + } + }, + "response": [] } ], "auth": { @@ -710,7 +768,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NjM1NTkxMTAsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NjM2NDU1MTAsImlhdCI6MTY2MzU1OTExMCwidXNlcm5hbWUiOiJhZG1pblBDIn0.LG1JXZkuTdMaqnXn5WMJvnysNkHHbc4HLe_qZPWz_nM", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njc0NzcwNTUsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njc1NjM0NTUsImlhdCI6MTY2NzQ3NzA1NSwidXNlcm5hbWUiOiJhZG1pblBDIn0.VMJ0ZKn895uvkMDjg2fw3p4trVCUx9ltVFKeP7QmYpo", "type": "string" }, { @@ -1322,6 +1380,37 @@ } }, "response": [] + }, + { + "name": "Import KMZ File", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [] + } + ] + }, + "url": { + "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/waylines/file/upload", + "host": [ + "{{base_url}}{{wayline_version}}" + ], + "path": [ + "workspaces", + "{{workspace_id}}", + "waylines", + "file", + "upload" + ] + } + }, + "response": [] } ], "auth": { @@ -1329,7 +1418,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTU0NDk2MDIsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTU1MzYwMDIsImlhdCI6MTY1NTQ0OTYwMiwidXNlcm5hbWUiOiJhZG1pblBDIn0.YZWHJ65Pl_DT2Ampxk0WC01KD_fNTm_rYVUBIHAZD-4", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg2NTk4MjYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg3NDYyMjYsImlhdCI6MTY2ODY1OTgyNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ykCpfJcReeb3etUzmNMQk1n0vaoDT6dl47J_aHRoTbU", "type": "string" }, { @@ -1370,7 +1459,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"\",\r\n \"file_id\": \"\",\r\n \"dock_sn\": \"\",\r\n \"type\": \"\",\r\n \"immediate\": false\r\n}", + "raw": "{\r\n \"name\": \"\",\r\n \"file_id\": \"\",\r\n \"dock_sn\": \"\",\r\n \"wayline_type\": 0,\r\n \"task_type\": 0,\r\n \"execute_time\": 123456789123,\r\n \"rth_altitude\": 20,\r\n \"out_of_control_action\": 1\r\n}", "options": { "raw": { "language": "json" @@ -1421,20 +1510,31 @@ "response": [] }, { - "name": "Execute Job", + "name": "Cancel the jobs", "request": { - "method": "POST", + "method": "DELETE", "header": [], "url": { - "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs/{{plan_id}}", + "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs", "host": [ "{{base_url}}{{wayline_version}}" ], "path": [ "workspaces", "{{workspace_id}}", - "jobs", - "{{plan_id}}" + "jobs" + ], + "query": [ + { + "key": "job_id", + "value": null, + "disabled": true + }, + { + "key": "job_id", + "value": null, + "disabled": true + } ] } }, @@ -1446,7 +1546,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NTU4OTA5NTQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NTU5NzczNTQsImlhdCI6MTY1NTg5MDk1NCwidXNlcm5hbWUiOiJhZG1pblBDIn0.fd0iIzCd71LDUE6ixexUJvo-YqtnSCqRx-790snCyBI", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg2NTk4MjYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg3NDYyMjYsImlhdCI6MTY2ODY1OTgyNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ykCpfJcReeb3etUzmNMQk1n0vaoDT6dl47J_aHRoTbU", "type": "string" }, { @@ -1506,7 +1606,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NjE5NTQwMTQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NjIwNDA0MTQsImlhdCI6MTY2MTk1NDAxNCwidXNlcm5hbWUiOiJhZG1pblBDIn0.GgCh575h2-HvYvdGZIKBW50r0F6CPACQn4ceAVzJfCU", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg0MzE5MzQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg1MTgzMzQsImlhdCI6MTY2ODQzMTkzNCwidXNlcm5hbWUiOiJhZG1pblBDIn0.QU9xHBeQPHJ2V1vXQcGGWRQ-gYEOWDpaTTXIQga85BU", "type": "string" }, { diff --git a/pom.xml b/pom.xml index e71dc5f..dd1207c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.dji cloud-api-sample - 1.1.0 + 1.3.0 cloud-api-sample @@ -156,6 +156,30 @@ org.springframework.boot spring-boot-starter-aop + + + org.dom4j + dom4j + 2.1.3 + + + + jaxen + jaxen + + + + org.bouncycastle + bcpkix-jdk15on + 1.69 + + + + org.springframework.boot + spring-boot-starter-validation + + + diff --git a/sql/cloud_sample.sql b/sql/cloud_sample.sql index 993aabd..47d2656 100644 --- a/sql/cloud_sample.sql +++ b/sql/cloud_sample.sql @@ -127,7 +127,12 @@ VALUES (15,1,90742,0,'L1',NULL), (16,2,56,0,'DJI Smart Controller','Remote control for M300'), (17,2,119,0,'DJI RC Plus','Remote control for M30'), - (18,3,1,0,'DJI Dock',''); + (18,3,1,0,'DJI Dock',''), + (19,0,77,0,'Mavic 3E',NULL), + (20,0,77,1,'Mavic 3T',NULL), + (21,1,66,0,'Mavic 3E Camera',NULL), + (22,1,67,0,'Mavic 3T Camera',NULL), + (23,2,144,0,'DJI RC Pro','Remote control for Mavic 3E/T'); /*!40000 ALTER TABLE `manage_device_dictionary` ENABLE KEYS */; UNLOCK TABLES; @@ -431,9 +436,16 @@ CREATE TABLE `wayline_job` ( `file_id` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'The wayline file used for this job.', `dock_sn` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'Which dock executes the job.', `workspace_id` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'Which workspace the current job belongs to.', - `bid` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'The bid used to execute the job, and the subsequent progress of the job is reported using this bid.', - `type` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'The type of the job. Available: wayline.', + `task_type` int NOT NULL, + `wayline_type` int NOT NULL COMMENT 'The template type of the wayline.', + `execute_time` bigint NOT NULL, `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'The name of the creator.', + `end_time` bigint DEFAULT NULL COMMENT 'end time of the job.', + `error_code` int DEFAULT NULL, + `status` int NOT NULL COMMENT '1: pending; 2: in progress; 3: success; 4: cancel; 5: failed', + `rth_altitude` int NOT NULL COMMENT 'return to home altitude. min: 20m; max: 500m', + `out_of_control` int NOT NULL COMMENT 'out of control action. 0: go home; 1: hover; 2: landing;', + `media_count` int NOT NULL DEFAULT '0', `create_time` bigint NOT NULL, `update_time` bigint NOT NULL, PRIMARY KEY (`id`), @@ -441,7 +453,6 @@ CREATE TABLE `wayline_job` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='Wayline mission information of the dock.'; - /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java b/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java index 0a65eed..6ba3f95 100644 --- a/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java +++ b/src/main/java/com/dji/sample/common/error/LiveErrorEnum.java @@ -67,9 +67,9 @@ public enum LiveErrorEnum implements IErrorInfo { * @return enumeration object */ public static LiveErrorEnum find(int code) { - + final int MOD = 100_000; for (LiveErrorEnum errorEnum : LiveErrorEnum.class.getEnumConstants()) { - if (errorEnum.code == code) { + if (errorEnum.code % MOD == code % MOD) { return errorEnum; } } diff --git a/src/main/java/com/dji/sample/common/util/JwtUtil.java b/src/main/java/com/dji/sample/common/util/JwtUtil.java index 629cea1..918f1d7 100644 --- a/src/main/java/com/dji/sample/common/util/JwtUtil.java +++ b/src/main/java/com/dji/sample/common/util/JwtUtil.java @@ -2,7 +2,6 @@ package com.dji.sample.common.util; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; -import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.DecodedJWT; @@ -85,14 +84,7 @@ public class JwtUtil { * @throws TokenExpiredException */ public static DecodedJWT verifyToken(String token) { - try { - JWTVerifier verifier = JWT.require(algorithm).build(); - return verifier.verify(token); - } catch (Exception e) { - log.error(e.getMessage()); - e.printStackTrace(); - return null; - } + return JWT.require(algorithm).build().verify(token); } /** @@ -101,7 +93,13 @@ public class JwtUtil { * @return custom claim */ public static Optional parseToken(String token) { - DecodedJWT jwt = verifyToken(token); - return jwt == null ? Optional.empty() : Optional.of(new CustomClaim(jwt.getClaims())); + DecodedJWT jwt; + try { + jwt = verifyToken(token); + } catch (Exception e) { + e.printStackTrace(); + return Optional.empty(); + } + return Optional.of(new CustomClaim(jwt.getClaims())); } } diff --git a/src/main/java/com/dji/sample/common/util/SpringBeanUtils.java b/src/main/java/com/dji/sample/common/util/SpringBeanUtils.java new file mode 100644 index 0000000..23e8966 --- /dev/null +++ b/src/main/java/com/dji/sample/common/util/SpringBeanUtils.java @@ -0,0 +1,30 @@ +package com.dji.sample.common.util; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Component +public class SpringBeanUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringBeanUtils.applicationContext = applicationContext; + } + + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } +} diff --git a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java index 1d5eb14..b0a9fbf 100644 --- a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java +++ b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.dji.sample.component; import com.dji.sample.common.model.ResponseResult; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @@ -30,4 +31,10 @@ public class GlobalExceptionHandler { e.printStackTrace(); return ResponseResult.error("A null object appeared."); } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { + return ResponseResult.error(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + } + } diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java index 4b278a0..51b138d 100644 --- a/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java +++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttMessageChannel.java @@ -152,4 +152,19 @@ public class MqttMessageChannel { return new DirectChannel(); } + @Bean(name = ChannelName.INBOUND_REQUESTS_FLIGHT_TASK_RESOURCE_GET) + public MessageChannel requestsFlightTaskResourceGet() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_PROPERTY_SET_REPLY) + public MessageChannel propertySetReply() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_REQUESTS_CONFIG) + public MessageChannel requestsConfig() { + return new DirectChannel(); + } + } diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java b/src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java index 3047ea5..8aa9112 100644 --- a/src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java +++ b/src/main/java/com/dji/sample/component/mqtt/handler/InboundMessageRouter.java @@ -1,6 +1,8 @@ package com.dji.sample.component.mqtt.handler; +import com.dji.sample.common.util.SpringBeanUtils; import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.DeviceTopicEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.integration.annotation.Router; import org.springframework.integration.mqtt.support.MqttHeaders; @@ -10,12 +12,8 @@ import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Component; -import javax.annotation.Resource; import java.util.Collection; import java.util.Collections; -import java.util.regex.Pattern; - -import static com.dji.sample.component.mqtt.model.TopicConst.*; /** * @@ -27,48 +25,6 @@ import static com.dji.sample.component.mqtt.model.TopicConst.*; @Slf4j public class InboundMessageRouter extends AbstractMessageRouter { - @Resource(name = ChannelName.INBOUND) - private MessageChannel inboundChannel; - - @Resource(name = ChannelName.INBOUND_STATUS) - private MessageChannel statusChannel; - - @Resource(name = ChannelName.INBOUND_STATE) - private MessageChannel stateChannel; - - @Resource(name = ChannelName.DEFAULT) - private MessageChannel defaultChannel; - - @Resource(name = ChannelName.INBOUND_SERVICE_REPLY) - private MessageChannel serviceReplyChannel; - - @Resource(name = ChannelName.INBOUND_OSD) - private MessageChannel osdChannel; - - @Resource(name = ChannelName.INBOUND_REQUESTS) - private MessageChannel requestsChannel; - - @Resource(name = ChannelName.INBOUND_EVENTS) - private MessageChannel eventsChannel; - - private static final Pattern PATTERN_TOPIC_STATUS = - Pattern.compile("^" + BASIC_PRE + PRODUCT + REGEX_SN + STATUS_SUF + "$"); - - private static final Pattern PATTERN_TOPIC_STATE = - Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + STATE_SUF + "$"); - - private static final Pattern PATTERN_TOPIC_SERVICE_REPLY = - Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + SERVICES_SUF + _REPLY_SUF + "$"); - - private static final Pattern PATTERN_TOPIC_OSD = - Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + OSD_SUF + "$"); - - private static final Pattern PATTERN_TOPIC_REQUESTS = - Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + REQUESTS_SUF + "$"); - - private static final Pattern PATTERN_TOPIC_EVENTS = - Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + EVENTS_SUF + "$"); - /** * All mqtt broker messages will arrive here before distributing them to different channels. * @param message message from mqtt broker @@ -81,38 +37,11 @@ public class InboundMessageRouter extends AbstractMessageRouter { String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); byte[] payload = (byte[])message.getPayload(); - // osd - if (PATTERN_TOPIC_OSD.matcher(topic).matches()) { - return Collections.singleton(osdChannel); - } - log.debug("received topic :{} \t payload :{}", topic, new String(payload)); - // status - if (PATTERN_TOPIC_STATUS.matcher(topic).matches()) { - return Collections.singleton(statusChannel); - } - - // state - if (PATTERN_TOPIC_STATE.matcher(topic).matches()) { - return Collections.singleton(stateChannel); - } - - // services_reply - if (PATTERN_TOPIC_SERVICE_REPLY.matcher(topic).matches()) { - return Collections.singleton(serviceReplyChannel); - } - - // requests - if (PATTERN_TOPIC_REQUESTS.matcher(topic).matches()) { - return Collections.singleton(requestsChannel); - } - - // events - if (PATTERN_TOPIC_EVENTS.matcher(topic).matches()) { - return Collections.singleton(eventsChannel); - } + DeviceTopicEnum topicEnum = DeviceTopicEnum.find(topic); + MessageChannel bean = (MessageChannel) SpringBeanUtils.getBean(topicEnum.getBeanName()); - return Collections.singleton(defaultChannel); + return Collections.singleton(bean); } } diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/PropertySetReplyHandler.java b/src/main/java/com/dji/sample/component/mqtt/handler/PropertySetReplyHandler.java new file mode 100644 index 0000000..9b503eb --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/handler/PropertySetReplyHandler.java @@ -0,0 +1,40 @@ +package com.dji.sample.component.mqtt.handler; + +import com.dji.sample.component.mqtt.model.Chan; +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * @author sean + * @version 1.2 + * @date 2022/9/9 + */ +@Component +public class PropertySetReplyHandler { + + @Autowired + private ObjectMapper mapper; + + /** + * Handle the reply message from the pilot side to the on-demand video. + * @param message reply message + * @throws IOException + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_PROPERTY_SET_REPLY) + public void serviceReply(Message message) throws IOException { + byte[] payload = (byte[])message.getPayload(); + + CommonTopicReceiver receiver = mapper.readValue(payload, new TypeReference() {}); + Chan> chan = Chan.getInstance(); + // Put the message to the chan object. + chan.put(receiver); + } +} diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java b/src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java index 753bad2..95e0f7d 100644 --- a/src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java +++ b/src/main/java/com/dji/sample/component/mqtt/handler/RequestsRouter.java @@ -11,6 +11,7 @@ import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; import java.io.IOException; +import java.util.Arrays; /** * @author sean @@ -37,13 +38,8 @@ public class RequestsRouter { }) .route( receiver -> RequestsMethodEnum.find(receiver.getMethod()), - mapping -> { - mapping.channelMapping(RequestsMethodEnum.STORAGE_CONFIG_GET, ChannelName.INBOUND_REQUESTS_STORAGE_CONFIG_GET); - mapping.channelMapping(RequestsMethodEnum.AIRPORT_BIND_STATUS, ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS); - mapping.channelMapping(RequestsMethodEnum.AIRPORT_ORGANIZATION_GET, ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_GET); - mapping.channelMapping(RequestsMethodEnum.AIRPORT_ORGANIZATION_BIND, ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND); - mapping.channelMapping(RequestsMethodEnum.UNKNOWN, ChannelName.DEFAULT); - }) + mapping -> Arrays.stream(RequestsMethodEnum.values()).forEach( + methodEnum -> mapping.channelMapping(methodEnum, methodEnum.getChannelName()))) .get(); } } diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java b/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java index 1851fd9..c0115ab 100644 --- a/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java +++ b/src/main/java/com/dji/sample/component/mqtt/handler/ServicesReplyHandler.java @@ -1,6 +1,10 @@ package com.dji.sample.component.mqtt.handler; -import com.dji.sample.component.mqtt.model.*; +import com.dji.sample.component.mqtt.model.Chan; +import com.dji.sample.component.mqtt.model.ChannelName; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.component.mqtt.model.ServiceReply; +import com.dji.sample.manage.model.enums.LogsFileMethodEnum; import com.dji.sample.manage.model.receiver.LogsFileUploadList; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -32,7 +36,7 @@ public class ServicesReplyHandler { byte[] payload = (byte[])message.getPayload(); CommonTopicReceiver receiver = mapper.readValue(payload, new TypeReference() {}); - if (ServicesMethodEnum.FILE_UPLOAD_LIST.getMethod().equals(receiver.getMethod())) { + if (LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod().equals(receiver.getMethod())) { LogsFileUploadList list = mapper.convertValue(receiver.getData(), new TypeReference() {}); receiver.setData(list); } else { diff --git a/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java index 83913c4..cb5a0f0 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java @@ -68,4 +68,10 @@ public class ChannelName { public static final String INBOUND_EVENTS_OTA_PROGRESS = "inboundEventsOtaProgress"; public static final String INBOUND_EVENTS_FILE_UPLOAD_PROGRESS = "inboundEventsFileUploadProgress"; + + public static final String INBOUND_REQUESTS_FLIGHT_TASK_RESOURCE_GET = "inboundEventsFlightTaskResourceGet"; + + public static final String INBOUND_PROPERTY_SET_REPLY = "inboundPropertySetReply"; + + public static final String INBOUND_REQUESTS_CONFIG = "inboundRequestsConfig"; } diff --git a/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java index 8770bfc..6090338 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/CommonTopicReceiver.java @@ -31,5 +31,4 @@ public class CommonTopicReceiver { private Integer needReply; - private String from; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/model/ConfigScopeEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/ConfigScopeEnum.java new file mode 100644 index 0000000..7cd4479 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/ConfigScopeEnum.java @@ -0,0 +1,32 @@ +package com.dji.sample.component.mqtt.model; + +import com.dji.sample.manage.service.IRequestsConfigService; +import com.dji.sample.manage.service.impl.ConfigProductServiceImpl; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Getter +public enum ConfigScopeEnum { + + PRODUCT("product", ConfigProductServiceImpl.class); + + String scope; + + Class clazz; + + ConfigScopeEnum(String scope, Class clazz) { + this.scope = scope; + this.clazz = clazz; + } + + public static Optional find(String scope) { + return Arrays.stream(ConfigScopeEnum.values()).filter(scopeEnum -> scopeEnum.scope.equals(scope)).findAny(); + } +} diff --git a/src/main/java/com/dji/sample/component/mqtt/model/DeviceTopicEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/DeviceTopicEnum.java new file mode 100644 index 0000000..00ab2a7 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/DeviceTopicEnum.java @@ -0,0 +1,46 @@ +package com.dji.sample.component.mqtt.model; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.regex.Pattern; + +import static com.dji.sample.component.mqtt.model.TopicConst.*; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/28 + */ +@Getter +public enum DeviceTopicEnum { + + STATUS(Pattern.compile("^" + BASIC_PRE + PRODUCT + REGEX_SN + STATUS_SUF + "$"), ChannelName.INBOUND_STATUS), + + STATE(Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + STATE_SUF + "$"), ChannelName.INBOUND_STATE), + + SERVICE_REPLY(Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + SERVICES_SUF + _REPLY_SUF + "$"), ChannelName.INBOUND_SERVICE_REPLY), + + OSD(Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + OSD_SUF + "$"), ChannelName.INBOUND_OSD), + + REQUESTS(Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + REQUESTS_SUF + "$"), ChannelName.INBOUND_REQUESTS), + + EVENTS(Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + EVENTS_SUF + "$"), ChannelName.INBOUND_EVENTS), + + PROPERTY_SET_REPLY(Pattern.compile("^" + THING_MODEL_PRE + PRODUCT + REGEX_SN + PROPERTY_SUF + SET_SUF + _REPLY_SUF + "$"), ChannelName.INBOUND_PROPERTY_SET_REPLY), + + UNKNOWN(null, ChannelName.DEFAULT); + + Pattern pattern; + + String beanName; + + DeviceTopicEnum(Pattern pattern, String beanName) { + this.pattern = pattern; + this.beanName = beanName; + } + + public static DeviceTopicEnum find(String topic) { + return Arrays.stream(DeviceTopicEnum.values()).filter(topicEnum -> topicEnum.pattern.matcher(topic).matches()).findAny().orElse(UNKNOWN); + } +} diff --git a/src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java index c4d26aa..1597e21 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/EventsReceiver.java @@ -1,7 +1,10 @@ package com.dji.sample.component.mqtt.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; /** * @author sean @@ -10,6 +13,9 @@ import lombok.Data; */ @Data @JsonIgnoreProperties(ignoreUnknown = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor public class EventsReceiver { private Integer result; diff --git a/src/main/java/com/dji/sample/component/mqtt/model/EventsResultStatusEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/EventsResultStatusEnum.java index 321e2f8..dd37ac6 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/EventsResultStatusEnum.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/EventsResultStatusEnum.java @@ -28,6 +28,8 @@ public enum EventsResultStatusEnum { TIMEOUT("timeout", true), + PARTIALLY_DONE("partially_done", true), + UNKNOWN("unknown", false); String desc; diff --git a/src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java b/src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java index f13ec92..242fd3d 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/MapKeyConst.java @@ -28,4 +28,9 @@ public final class MapKeyConst { public static final String LIST = "list"; public static final String MODULE_LIST = "module_list"; + + public static final String FLIGHT_ID = "flight_id"; + + public static final String FLIGHT_IDS = "flight_ids"; + } diff --git a/src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java index ddfa599..8d04ceb 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/RequestsMethodEnum.java @@ -1,5 +1,7 @@ package com.dji.sample.component.mqtt.model; +import lombok.Getter; + import java.util.Arrays; /** @@ -7,26 +9,30 @@ import java.util.Arrays; * @version 1.0 * @date 2022/5/25 */ +@Getter public enum RequestsMethodEnum { - STORAGE_CONFIG_GET("storage_config_get"), + STORAGE_CONFIG_GET("storage_config_get", ChannelName.INBOUND_REQUESTS_STORAGE_CONFIG_GET), + + AIRPORT_BIND_STATUS("airport_bind_status", ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS), + + AIRPORT_ORGANIZATION_BIND("airport_organization_bind", ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND), - AIRPORT_BIND_STATUS("airport_bind_status"), + AIRPORT_ORGANIZATION_GET("airport_organization_get", ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_GET), - AIRPORT_ORGANIZATION_BIND("airport_organization_bind"), + FLIGHT_TASK_RESOURCE_GET("flighttask_resource_get", ChannelName.INBOUND_REQUESTS_FLIGHT_TASK_RESOURCE_GET), - AIRPORT_ORGANIZATION_GET("airport_organization_get"), + CONFIG("config", ChannelName.INBOUND_REQUESTS_CONFIG), - UNKNOWN("Unknown"); + UNKNOWN("Unknown", ChannelName.DEFAULT); private String method; - RequestsMethodEnum(String method) { - this.method = method; - } + private String channelName; - public String getMethod() { - return method; + RequestsMethodEnum(String method, String channelName) { + this.method = method; + this.channelName = channelName; } public static RequestsMethodEnum find(String method) { diff --git a/src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java deleted file mode 100644 index 40ca0fc..0000000 --- a/src/main/java/com/dji/sample/component/mqtt/model/ServicesMethodEnum.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.dji.sample.component.mqtt.model; - -import java.util.Arrays; - -/** - * @author sean.zhou - * @date 2021/11/22 - * @version 0.1 - */ -public enum ServicesMethodEnum { - - LIVE_START_PUSH("live_start_push", false), - - LIVE_STOP_PUSH("live_stop_push", false), - - LIVE_SET_QUALITY("live_set_quality", false), - - FLIGHTTASK_CREATE("flighttask_create", false), - - DEBUG_MODE_OPEN("debug_mode_open", false), - - DEBUG_MODE_CLOSE("debug_mode_close", false), - - SUPPLEMENT_LIGHT_OPEN("supplement_light_open", false), - - SUPPLEMENT_LIGHT_CLOSE("supplement_light_close", false), - - RETURN_HOME("return_home", false), - - SDR_WORKMODE_SWITCH("sdr_workmode_switch", false), - - DEVICE_REBOOT("device_reboot", true), - - DRONE_OPEN("drone_open", true), - - DRONE_CLOSE("drone_close", true), - - DEVICE_CHECK("device_check", true), - - DRONE_FORMAT("drone_format", true), - - DEVICE_FORMAT("device_format", true), - - COVER_OPEN("cover_open", true), - - COVER_CLOSE("cover_close", true), - - PUTTER_OPEN("putter_open", true), - - PUTTER_CLOSE("putter_close", true), - - CHARGE_OPEN("charge_open", true), - - CHARGE_CLOSE("charge_close", true), - - OTA_CREATE("ota_create", true), - - FILE_UPLOAD_LIST("fileupload_list", false), - - FILE_UPLOAD_START("fileupload_start", true), - - FILE_UPLOAD_UPDATE("fileupload_update", false), - - UNKNOWN("unknown", false); - - private String method; - - private Boolean progress; - - ServicesMethodEnum(String method, Boolean progress) { - this.method = method; - this.progress = progress; - } - - public static ServicesMethodEnum find(String method) { - return Arrays.stream(ServicesMethodEnum.values()) - .filter(methodEnum -> methodEnum.method.equals(method)) - .findAny() - .orElse(UNKNOWN); - } - - public String getMethod() { - return method; - } - - public Boolean getProgress() { - return progress; - } -} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/model/SetReply.java b/src/main/java/com/dji/sample/component/mqtt/model/SetReply.java new file mode 100644 index 0000000..2c7f453 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/SetReply.java @@ -0,0 +1,14 @@ +package com.dji.sample.component.mqtt.model; + +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/28 + */ +@Data +public class SetReply { + + private Integer result; +} diff --git a/src/main/java/com/dji/sample/component/mqtt/model/SetReplyStatusResultEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/SetReplyStatusResultEnum.java new file mode 100644 index 0000000..5a127e4 --- /dev/null +++ b/src/main/java/com/dji/sample/component/mqtt/model/SetReplyStatusResultEnum.java @@ -0,0 +1,30 @@ +package com.dji.sample.component.mqtt.model; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/28 + */ +@Getter +public enum SetReplyStatusResultEnum { + + SUCCESS(0, "success"), + + FAILED(1, "failed"), + + TIMEOUT(2, "timeout"), + + UNKNOWN(-1, "unknown"); + + int val; + + String desc; + + SetReplyStatusResultEnum(int val, String desc) { + this.val = val; + this.desc = desc; + } + +} diff --git a/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java index dc9df7c..215995a 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/TopicConst.java @@ -28,6 +28,10 @@ public class TopicConst { public static final String EVENTS_SUF = "/events"; + public static final String PROPERTY_SUF = "/property"; + + public static final String SET_SUF = "/set"; + public static final String REGEX_SN = "[A-Za-z0-9]+"; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java index 15079a6..40f7f18 100644 --- a/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java +++ b/src/main/java/com/dji/sample/component/mqtt/service/IMessageSenderService.java @@ -3,8 +3,6 @@ package com.dji.sample.component.mqtt.service; import com.dji.sample.component.mqtt.model.CommonTopicResponse; import com.dji.sample.component.mqtt.model.ServiceReply; -import java.util.Optional; - /** * @author sean.zhou * @version 0.1 @@ -33,7 +31,7 @@ public interface IMessageSenderService { * @param response notification of whether the start is successful. * @return */ - Optional publishWithReply(String topic, CommonTopicResponse response); + ServiceReply publishWithReply(String topic, CommonTopicResponse response); /** * Send live streaming start message and receive a response at the same time. @@ -44,5 +42,5 @@ public interface IMessageSenderService { * @param * @return */ - Optional publishWithReply(Class clazz, String topic, CommonTopicResponse response, int retryTime); + T publishWithReply(Class clazz, String topic, CommonTopicResponse response, int retryTime); } diff --git a/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java index 10bb7e2..89e5093 100644 --- a/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java +++ b/src/main/java/com/dji/sample/component/mqtt/service/impl/MessageSenderServiceImpl.java @@ -12,7 +12,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; /** @@ -49,28 +48,28 @@ public class MessageSenderServiceImpl implements IMessageSenderService { } } - public Optional publishWithReply(String topic, CommonTopicResponse response) { + public ServiceReply publishWithReply(String topic, CommonTopicResponse response) { return this.publishWithReply(ServiceReply.class, topic, response, 2); } - public Optional publishWithReply(Class clazz, String topic, CommonTopicResponse response, int retryTime) { + public T publishWithReply(Class clazz, String topic, CommonTopicResponse response, int retryTime) { AtomicInteger time = new AtomicInteger(0); // Retry three times - while (time.getAndIncrement() < retryTime) { + while (time.getAndIncrement() <= retryTime) { this.publish(topic, response); Chan> chan = Chan.getInstance(); // If the message is not received in 0.5 seconds then resend it again. - CommonTopicReceiver receiver = chan.get(response.getMethod()); + CommonTopicReceiver receiver = chan.get(response.getTid()); if (receiver == null) { continue; } // Need to match tid and bid. if (receiver.getTid().equals(response.getTid()) && receiver.getBid().equals(response.getBid())) { - return Optional.ofNullable(receiver.getData()); + return receiver.getData(); } } - return Optional.empty(); + throw new RuntimeException("No message reply received."); } } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/oss/service/IOssService.java b/src/main/java/com/dji/sample/component/oss/service/IOssService.java index 53746ef..f8441dd 100644 --- a/src/main/java/com/dji/sample/component/oss/service/IOssService.java +++ b/src/main/java/com/dji/sample/component/oss/service/IOssService.java @@ -2,6 +2,7 @@ package com.dji.sample.component.oss.service; import com.dji.sample.media.model.CredentialsDTO; +import java.io.InputStream; import java.net.URL; /** @@ -41,5 +42,7 @@ public interface IOssService { * @param objectKey * @return */ - byte[] getObject(String bucket, String objectKey); + InputStream getObject(String bucket, String objectKey); + + void putObject(String bucket, String objectKey, InputStream input); } diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java index d709b07..d855261 100644 --- a/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java +++ b/src/main/java/com/dji/sample/component/oss/service/impl/AliyunOssServiceImpl.java @@ -4,6 +4,9 @@ import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.model.OSSObject; +import com.aliyun.oss.model.ObjectMetadata; +import com.aliyun.oss.model.PutObjectRequest; +import com.aliyun.oss.model.PutObjectResult; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; @@ -93,18 +96,30 @@ public class AliyunOssServiceImpl implements IOssService { } @Override - public byte[] getObject(String bucket, String objectKey) { + public InputStream getObject(String bucket, String objectKey) { OSS ossClient = this.createClient(); OSSObject object = ossClient.getObject(bucket, objectKey); - try (InputStream stream = object.getObjectContent()) { - return stream.readAllBytes(); + try (InputStream input = object.getObjectContent()) { + return input; } catch (IOException e) { e.printStackTrace(); } finally { ossClient.shutdown(); } - return new byte[0]; + return InputStream.nullInputStream(); + } + + @Override + public void putObject(String bucket, String objectKey, InputStream input) { + OSS ossClient = this.createClient(); + if (ossClient.doesObjectExist(bucket, objectKey)) { + ossClient.shutdown(); + throw new RuntimeException("The filename already exists."); + } + PutObjectResult objectResult = ossClient.putObject(new PutObjectRequest(bucket, objectKey, input, new ObjectMetadata())); + ossClient.shutdown(); + log.info("Upload File: {}", objectResult.getETag()); } private OSS createClient() { diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java index f2bcdc5..b9cdfd3 100644 --- a/src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java +++ b/src/main/java/com/dji/sample/component/oss/service/impl/AmazonS3ServiceImpl.java @@ -5,9 +5,7 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.BucketCrossOriginConfiguration; -import com.amazonaws.services.s3.model.CORSRule; -import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.*; import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; @@ -18,6 +16,7 @@ import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.model.enums.OssTypeEnum; import com.dji.sample.component.oss.service.IOssService; import com.dji.sample.media.model.CredentialsDTO; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -34,6 +33,7 @@ import java.util.List; * @version 1.0 * @date 2022/4/27 */ +@Slf4j @Service public class AmazonS3ServiceImpl implements IOssService { @@ -83,18 +83,29 @@ public class AmazonS3ServiceImpl implements IOssService { return true; } - public byte[] getObject(String bucket, String objectKey) { + public InputStream getObject(String bucket, String objectKey) { AmazonS3 client = this.createClient(); S3Object object = client.getObject(bucket, objectKey); - - try (InputStream stream = object.getObjectContent().getDelegateStream()) { - return stream.readAllBytes(); + try (InputStream input = object.getObjectContent().getDelegateStream()) { + return input; } catch (IOException e) { e.printStackTrace(); } finally { client.shutdown(); } - return new byte[0]; + return InputStream.nullInputStream(); + } + + @Override + public void putObject(String bucket, String objectKey, InputStream input) { + AmazonS3 client = this.createClient(); + if (client.doesObjectExist(bucket, objectKey)) { + client.shutdown(); + throw new RuntimeException("The filename already exists."); + } + PutObjectResult objectResult = client.putObject(new PutObjectRequest(bucket, objectKey, input, new ObjectMetadata())); + client.shutdown(); + log.info("Upload File: {}", objectResult.toString()); } private AmazonS3 createClient() { diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java b/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java index 009a1fb..b162751 100644 --- a/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java +++ b/src/main/java/com/dji/sample/component/oss/service/impl/MinIOServiceImpl.java @@ -4,10 +4,7 @@ import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.model.enums.OssTypeEnum; import com.dji.sample.component.oss.service.IOssService; import com.dji.sample.media.model.CredentialsDTO; -import io.minio.GetObjectArgs; -import io.minio.GetPresignedObjectUrlArgs; -import io.minio.MinioClient; -import io.minio.RemoveObjectArgs; +import io.minio.*; import io.minio.credentials.AssumeRoleProvider; import io.minio.errors.*; import io.minio.http.Method; @@ -15,11 +12,13 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Objects; /** * @author sean @@ -30,6 +29,8 @@ import java.security.NoSuchAlgorithmException; @Slf4j public class MinIOServiceImpl implements IOssService { + private MinioClient client; + @Autowired private OssConfiguration configuration; @@ -87,21 +88,44 @@ public class MinIOServiceImpl implements IOssService { } @Override - public byte[] getObject(String bucket, String objectKey) { - MinioClient client = this.createClient(); - try (InputStream objectResponse = client.getObject(GetObjectArgs.builder().bucket(bucket).object(objectKey).build())) { - return objectResponse.readAllBytes(); - } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) { + public InputStream getObject(String bucket, String objectKey) { + try { + GetObjectResponse object = this.createClient().getObject(GetObjectArgs.builder().bucket(bucket).object(objectKey).build()); + return new ByteArrayInputStream(object.readAllBytes()); + } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { e.printStackTrace(); } - return new byte[0]; + return InputStream.nullInputStream(); + } + + @Override + public void putObject(String bucket, String objectKey, InputStream input) { + try { + MinioClient client = this.createClient(); + client.statObject(StatObjectArgs.builder().bucket(bucket).object(objectKey).build()); + throw new RuntimeException("The filename already exists."); + } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) { + log.info("The file does not exist, start uploading."); + try { + ObjectWriteResponse response = client.putObject( + PutObjectArgs.builder().bucket(bucket).object(objectKey).stream(input, input.available(), 0).build()); + log.info("Upload File: {}", response.etag()); + } catch (MinioException | IOException | InvalidKeyException | NoSuchAlgorithmException ex) { + log.error("Failed to upload File {}.", objectKey); + ex.printStackTrace(); + } + } } private MinioClient createClient() { - return MinioClient.builder() + if (Objects.nonNull(this.client)) { + return this.client; + } + this.client = MinioClient.builder() .endpoint(configuration.getEndpoint()) .credentials(configuration.getAccessKey(), configuration.getSecretKey()) .region(configuration.getRegion()) .build(); + return this.client; } } diff --git a/src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java b/src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java index 9ac8059..e131889 100644 --- a/src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java +++ b/src/main/java/com/dji/sample/component/oss/service/impl/OssServiceContext.java @@ -7,6 +7,7 @@ import com.dji.sample.media.model.CredentialsDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.InputStream; import java.net.URL; import java.util.Arrays; import java.util.List; @@ -52,7 +53,11 @@ public class OssServiceContext { return this.ossService.deleteObject(bucket, objectKey); } - public byte[] getObject(String bucket, String objectKey) { + public InputStream getObject(String bucket, String objectKey) { return this.ossService.getObject(bucket, objectKey); } + + public void putObject(String bucket, String objectKey, InputStream stream) { + this.ossService.putObject(bucket, objectKey, stream); + } } diff --git a/src/main/java/com/dji/sample/component/redis/RedisConst.java b/src/main/java/com/dji/sample/component/redis/RedisConst.java index 718ebee..1fce89d 100644 --- a/src/main/java/com/dji/sample/component/redis/RedisConst.java +++ b/src/main/java/com/dji/sample/component/redis/RedisConst.java @@ -34,4 +34,8 @@ public final class RedisConst { public static final String STATE_PAYLOAD_PREFIX = "payload" + DELIMITER; public static final String LOGS_FILE_PREFIX = "logs_file" + DELIMITER; + + public static final String WAYLINE_JOB = "wayline_job"; + + public static final String OSD_PREFIX = "osd" + DELIMITER; } diff --git a/src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java b/src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java index f5b1066..5e329c9 100644 --- a/src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java +++ b/src/main/java/com/dji/sample/component/redis/RedisOpsUtils.java @@ -3,6 +3,7 @@ package com.dji.sample.component.redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Set; @@ -184,4 +185,57 @@ public class RedisOpsUtils { public Long listLen(String key) { return redisTemplate.opsForList().size(key); } + + /** + * ZADD + * @param key + * @param value + * @param score + */ + public Boolean zAdd(String key, Object value, double score) { + return redisTemplate.opsForZSet().add(key, value, score); + } + + /** + * ZREM + * @param key + * @param value + */ + public Boolean zRemove(String key, Object... value) { + return redisTemplate.opsForZSet().remove(key, value) > 0; + } + /** + * ZRANGE + * @param key + * @param start + * @param end + * @return + */ + public Set zRange(String key, long start, long end) { + return redisTemplate.opsForZSet().range(key, start, end); + } + + /** + * ZRANGE + * @param key + * @return + */ + public Object zGetMin(String key) { + Set objects = zRange(key, 0, 0); + if (CollectionUtils.isEmpty(objects)) { + return null; + } + return objects.iterator().next(); + } + + /** + * ZSCORE + * @param key + * @param value + * @return + */ + public Double zScore(String key, Object value) { + return redisTemplate.opsForZSet().score(key, value); + } + } diff --git a/src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java b/src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java index 9887d1e..eee6f48 100644 --- a/src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java +++ b/src/main/java/com/dji/sample/configuration/SpringBeanConfiguration.java @@ -35,6 +35,7 @@ public class SpringBeanConfiguration { objectMapper.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS); objectMapper.registerModules(timeModule); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); diff --git a/src/main/java/com/dji/sample/control/controller/DockController.java b/src/main/java/com/dji/sample/control/controller/DockController.java index 8b05ff8..7836e5f 100644 --- a/src/main/java/com/dji/sample/control/controller/DockController.java +++ b/src/main/java/com/dji/sample/control/controller/DockController.java @@ -1,13 +1,11 @@ package com.dji.sample.control.controller; import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.control.model.param.RemoteDebugParam; import com.dji.sample.control.service.IControlService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; /** * @author sean @@ -24,7 +22,8 @@ public class DockController { @PostMapping("/{sn}/jobs/{service_identifier}") public ResponseResult createControlJob(@PathVariable String sn, - @PathVariable("service_identifier") String serviceIdentifier) { - return controlService.controlDock(sn, serviceIdentifier); + @PathVariable("service_identifier") String serviceIdentifier, + @RequestBody(required = false) RemoteDebugParam param) { + return controlService.controlDock(sn, serviceIdentifier, param); } } diff --git a/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java b/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java new file mode 100644 index 0000000..eaf833f --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java @@ -0,0 +1,29 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.control.model.enums.BatteryStoreModeEnum; +import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BatteryStoreMode extends BasicDeviceProperty { + + private Integer value; + + @Override + public boolean valid() { + return Objects.nonNull(value) && BatteryStoreModeEnum.find(value).isPresent(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/BatteryStoreModeEnum.java b/src/main/java/com/dji/sample/control/model/enums/BatteryStoreModeEnum.java new file mode 100644 index 0000000..9b3d0a8 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/BatteryStoreModeEnum.java @@ -0,0 +1,29 @@ +package com.dji.sample.control.model.enums; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Getter +public enum BatteryStoreModeEnum { + + PLAN(1), + + EMERGENCY(2); + + Integer mode; + + BatteryStoreModeEnum(Integer mode) { + this.mode = mode; + } + + public static Optional find(int mode) { + return Arrays.stream(BatteryStoreModeEnum.values()).filter(modeEnum -> modeEnum.mode == mode).findAny(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java new file mode 100644 index 0000000..f7350f7 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java @@ -0,0 +1,81 @@ +package com.dji.sample.control.model.enums; + +import com.dji.sample.control.model.dto.BatteryStoreMode; +import com.dji.sample.manage.model.enums.StateSwitchReceiver; +import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import lombok.Getter; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Getter +public enum RemoteControlMethodEnum { + + DEBUG_MODE_OPEN("debug_mode_open", false, null), + + DEBUG_MODE_CLOSE("debug_mode_close", false, null), + + SUPPLEMENT_LIGHT_OPEN("supplement_light_open", false, null), + + SUPPLEMENT_LIGHT_CLOSE("supplement_light_close", false, null), + + RETURN_HOME("return_home", false, null), + + SDR_WORKMODE_SWITCH("sdr_workmode_switch", false, null), + + DEVICE_REBOOT("device_reboot", true, null), + + DRONE_OPEN("drone_open", true, null), + + DRONE_CLOSE("drone_close", true, null), + + DEVICE_CHECK("device_check", true, null), + + DRONE_FORMAT("drone_format", true, null), + + DEVICE_FORMAT("device_format", true, null), + + COVER_OPEN("cover_open", true, null), + + COVER_CLOSE("cover_close", true, null), + + PUTTER_OPEN("putter_open", true, null), + + PUTTER_CLOSE("putter_close", true, null), + + CHARGE_OPEN("charge_open", true, null), + + CHARGE_CLOSE("charge_close", true, null), + + BATTERY_MAINTENANCE_SWITCH("battery_maintenance_switch", true, StateSwitchReceiver.class), + + ALARM_STATE_SWITCH("alarm_state_switch", true, StateSwitchReceiver.class), + + BATTERY_STORE_MODE_SWITCH("battery_store_mode_switch", true, BatteryStoreMode.class), + + UNKNOWN("unknown", false, null); + + private String method; + + private Boolean progress; + + private Class clazz; + + RemoteControlMethodEnum(String method, Boolean progress, Class clazz) { + this.method = method; + this.progress = progress; + this.clazz = clazz; + } + + public static RemoteControlMethodEnum find(String method) { + return Arrays.stream(RemoteControlMethodEnum.values()) + .filter(methodEnum -> methodEnum.method.equals(method)) + .findAny() + .orElse(UNKNOWN); + } + +} diff --git a/src/main/java/com/dji/sample/control/model/param/RemoteDebugParam.java b/src/main/java/com/dji/sample/control/model/param/RemoteDebugParam.java new file mode 100644 index 0000000..4d48659 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/RemoteDebugParam.java @@ -0,0 +1,15 @@ +package com.dji.sample.control.model.param; + +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Data +public class RemoteDebugParam { + + private Integer action; + +} diff --git a/src/main/java/com/dji/sample/control/service/IControlService.java b/src/main/java/com/dji/sample/control/service/IControlService.java index 7f2de02..f04c138 100644 --- a/src/main/java/com/dji/sample/control/service/IControlService.java +++ b/src/main/java/com/dji/sample/control/service/IControlService.java @@ -2,6 +2,7 @@ package com.dji.sample.control.service; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.control.model.param.RemoteDebugParam; import org.springframework.messaging.MessageHeaders; /** @@ -11,8 +12,20 @@ import org.springframework.messaging.MessageHeaders; */ public interface IControlService { - ResponseResult controlDock(String sn, String serviceIdentifier); + /** + * Remotely debug the dock via commands. + * @param sn + * @param serviceIdentifier + * @param param + * @return + */ + ResponseResult controlDock(String sn, String serviceIdentifier, RemoteDebugParam param); + /** + * Handles multi-state command progress information. + * @param receiver + * @param headers + */ void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers); } diff --git a/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java b/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java index 2b0bbf4..511416d 100644 --- a/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java +++ b/src/main/java/com/dji/sample/control/service/impl/ControlServiceImpl.java @@ -1,5 +1,6 @@ package com.dji.sample.control.service.impl; +import com.dji.sample.common.error.CommonErrorEnum; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.component.mqtt.model.*; import com.dji.sample.component.mqtt.service.IMessageSenderService; @@ -8,9 +9,13 @@ import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.component.websocket.model.CustomWebSocketMessage; import com.dji.sample.component.websocket.service.ISendMessageService; import com.dji.sample.component.websocket.service.IWebSocketManageService; +import com.dji.sample.control.model.enums.RemoteControlMethodEnum; +import com.dji.sample.control.model.param.RemoteDebugParam; import com.dji.sample.control.service.IControlService; import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import com.dji.sample.manage.service.IDeviceService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -20,7 +25,7 @@ import org.springframework.integration.mqtt.support.MqttHeaders; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; -import java.util.Optional; +import java.util.Objects; import java.util.UUID; /** @@ -44,38 +49,51 @@ public class ControlServiceImpl implements IControlService { @Autowired private IWebSocketManageService webSocketManageService; + @Autowired + private IDeviceService deviceService; + @Autowired private ObjectMapper mapper; @Override - public ResponseResult controlDock(String sn, String serviceIdentifier) { - ServicesMethodEnum servicesMethodEnum = ServicesMethodEnum.find(serviceIdentifier); - if (servicesMethodEnum == ServicesMethodEnum.UNKNOWN) { + public ResponseResult controlDock(String sn, String serviceIdentifier, RemoteDebugParam param) { + RemoteControlMethodEnum controlMethodEnum = RemoteControlMethodEnum.find(serviceIdentifier); + if (RemoteControlMethodEnum.UNKNOWN == controlMethodEnum) { return ResponseResult.error("The " + serviceIdentifier + " method does not exist."); } - boolean isExist = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + sn) > 0; + + // Add parameter validation. + if (Objects.nonNull(controlMethodEnum.getClazz())) { + if (Objects.isNull(param)) { + return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT); + } + BasicDeviceProperty basicDeviceProperty = mapper.convertValue(param.getAction(), controlMethodEnum.getClazz()); + if (!basicDeviceProperty.valid()) { + return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT); + } + } + + boolean isExist = deviceService.checkDeviceOnline(sn); if (!isExist) { return ResponseResult.error("The dock is offline."); } String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + sn + TopicConst.SERVICES_SUF; String bid = UUID.randomUUID().toString(); - Optional serviceReplyOpt = messageSenderService.publishWithReply( + ServiceReply serviceReplyOpt = messageSenderService.publishWithReply( topic, CommonTopicResponse.builder() .tid(UUID.randomUUID().toString()) .bid(bid) .method(serviceIdentifier) .timestamp(System.currentTimeMillis()) - .data("") + .data(Objects.requireNonNullElse(param, "")) .build()); - if (serviceReplyOpt.isEmpty()) { - return ResponseResult.error("No message reply received."); - } + ServiceReply serviceReply = mapper.convertValue( - serviceReplyOpt.get(), new TypeReference>() {}); - if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) { + serviceReplyOpt, new TypeReference>() {}); + if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { return ResponseResult.error(serviceReply.getResult(), serviceReply.getOutput().getStatus()); } - if (servicesMethodEnum.getProgress()) { + if (controlMethodEnum.getProgress()) { redisOps.setWithExpire(serviceIdentifier + RedisConst.DELIMITER + bid, sn, RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); } @@ -126,7 +144,7 @@ public class ControlServiceImpl implements IControlService { .bid(receiver.getBid()) .method(receiver.getMethod()) .timestamp(System.currentTimeMillis()) - .data(ResponseResult.success()) + .data(RequestsReply.success()) .build()); } } diff --git a/src/main/java/com/dji/sample/manage/controller/DeviceController.java b/src/main/java/com/dji/sample/manage/controller/DeviceController.java index e9d80c0..602e8c9 100644 --- a/src/main/java/com/dji/sample/manage/controller/DeviceController.java +++ b/src/main/java/com/dji/sample/manage/controller/DeviceController.java @@ -1,21 +1,20 @@ package com.dji.sample.manage.controller; +import com.dji.sample.common.error.CommonErrorEnum; import com.dji.sample.common.model.PaginationData; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.component.mqtt.model.ChannelName; import com.dji.sample.component.mqtt.model.CommonTopicReceiver; import com.dji.sample.component.mqtt.model.CommonTopicResponse; -import com.dji.sample.component.websocket.service.ISendMessageService; import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO; -import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver; +import com.dji.sample.manage.model.enums.DeviceSetPropertyEnum; import com.dji.sample.manage.model.receiver.StatusGatewayReceiver; import com.dji.sample.manage.service.IDeviceService; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.mqtt.support.MqttHeaders; -import org.springframework.messaging.Message; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -34,9 +33,6 @@ public class DeviceController { @Autowired private IDeviceService deviceService; - @Autowired - private ISendMessageService sendMessageService; - /** * Handles the message that the drone goes online. * @param receiver The drone information is not empty. @@ -50,6 +46,8 @@ public class DeviceController { CommonTopicResponse.builder() .tid(receiver.getTid()) .bid(receiver.getBid()) + .timestamp(System.currentTimeMillis()) + .method(receiver.getMethod()) .build()); } } @@ -61,13 +59,15 @@ public class DeviceController { @ServiceActivator(inputChannel = ChannelName.INBOUND_STATUS_OFFLINE, outputChannel = ChannelName.OUTBOUND) public void deviceOffline(CommonTopicReceiver receiver) { - boolean offline = deviceService.deviceOffline(receiver.getData().getSn()); + boolean offline = deviceService.deviceOffline(receiver.getData()); if (offline) { // Notify pilot that the device is offline successfully. deviceService.publishStatusReply(receiver.getData().getSn(), CommonTopicResponse.builder() .tid(receiver.getTid()) .bid(receiver.getBid()) + .timestamp(System.currentTimeMillis()) + .method(receiver.getMethod()) .build()); } @@ -85,26 +85,6 @@ public class DeviceController { return ResponseResult.success(devicesList); } - /** - * Handle osd topic messages. - * @param message - */ - @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD) - public void osdRealTime(Message message) { - String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); - byte[] payload = (byte[])message.getPayload(); - deviceService.handleOSD(topic, payload); - } - - /** - * Receive the reported firmware version data. - * @param receiver - */ - @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_FIRMWARE_VERSION) - public void updateFirmwareVersion(FirmwareVersionReceiver receiver) { - deviceService.updateFirmwareVersion(receiver); - } - /** * After binding the device to the workspace, the device data can only be seen on the web. * @param device @@ -186,4 +166,27 @@ public class DeviceController { @RequestBody List upgradeDTOS) { return deviceService.createDeviceOtaJob(workspaceId, upgradeDTOS); } + + /** + * Set the property parameters of the drone. + * @param workspaceId + * @param dockSn + * @param param + * @return + */ + @PutMapping("/{workspace_id}/devices/{device_sn}/property") + public ResponseResult devicePropertySet(@PathVariable("workspace_id") String workspaceId, + @PathVariable("device_sn") String dockSn, + @RequestBody JsonNode param) { + if (param.size() != 1) { + return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT); + } + String property = param.fieldNames().next(); + Optional propertyEnumOpt = DeviceSetPropertyEnum.find(property); + if (propertyEnumOpt.isEmpty()) { + return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT); + } + deviceService.devicePropertySet(workspaceId, dockSn, propertyEnumOpt.get(), param.get(property)); + return ResponseResult.success(); + } } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java b/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java index 6154e94..c8bd144 100644 --- a/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java +++ b/src/main/java/com/dji/sample/manage/controller/LiveStreamController.java @@ -91,4 +91,9 @@ public class LiveStreamController { return liveStreamService.liveSetQuality(liveParam); } + @PostMapping("/streams/switch") + public ResponseResult liveLensChange(@RequestBody LiveTypeDTO liveParam) { + return liveStreamService.liveLensChange(liveParam); + } + } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/common/NtpServerProperties.java b/src/main/java/com/dji/sample/manage/model/common/NtpServerProperties.java new file mode 100644 index 0000000..7e562f3 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/common/NtpServerProperties.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.common; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Component +@ConfigurationProperties("ntp.server") +public class NtpServerProperties { + + public static String host; + + public void setHost(String host) { + NtpServerProperties.host = host; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java b/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java index 9366472..531e7ec 100644 --- a/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java +++ b/src/main/java/com/dji/sample/manage/model/dto/CapacityVideoDTO.java @@ -5,6 +5,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + /** * @author sean.zhou * @date 2021/11/22 @@ -21,4 +23,6 @@ public class CapacityVideoDTO { private String index; private String type; + + private List switchVideoTypes; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java b/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java index ffe341e..4a385ec 100644 --- a/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java +++ b/src/main/java/com/dji/sample/manage/model/dto/LiveTypeDTO.java @@ -23,4 +23,6 @@ public class LiveTypeDTO { @JsonProperty("video_quality") private Integer videoQuality; + private String videoType; + } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/NtpServerDTO.java b/src/main/java/com/dji/sample/manage/model/dto/NtpServerDTO.java new file mode 100644 index 0000000..27b05a5 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/NtpServerDTO.java @@ -0,0 +1,18 @@ +package com.dji.sample.manage.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class NtpServerDTO { + + private String ntpServerHost; +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java new file mode 100644 index 0000000..38f9428 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java @@ -0,0 +1,41 @@ +package com.dji.sample.manage.model.enums; + +import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import com.dji.sample.manage.model.receiver.DistanceLimitStatusReceiver; +import com.dji.sample.manage.model.receiver.HeightLimitReceiver; +import com.dji.sample.manage.model.receiver.ObstacleAvoidanceReceiver; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/27 + */ +@Getter +public enum DeviceSetPropertyEnum { + + NIGHT_LIGHTS_STATE("night_lights_state", StateSwitchReceiver.class), + + HEIGHT_LIMIT("height_limit", HeightLimitReceiver.class), + + DISTANCE_LIMIT_STATUS("distance_limit_status", DistanceLimitStatusReceiver.class), + + OBSTACLE_AVOIDANCE("obstacle_avoidance", ObstacleAvoidanceReceiver.class); + + + String property; + + Class clazz; + + DeviceSetPropertyEnum(String property, Class clazz) { + this.property = property; + this.clazz = clazz; + } + + public static Optional find(String property) { + return Arrays.stream(DeviceSetPropertyEnum.values()).filter(propertyEnum -> propertyEnum.property.equals(property)).findAny(); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/FirmwareMethodEnum.java b/src/main/java/com/dji/sample/manage/model/enums/FirmwareMethodEnum.java new file mode 100644 index 0000000..b185546 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/FirmwareMethodEnum.java @@ -0,0 +1,21 @@ +package com.dji.sample.manage.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Getter +public enum FirmwareMethodEnum { + + OTA_CREATE("ota_create"); + + private String method; + + FirmwareMethodEnum(String method) { + this.method = method; + } + +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/LiveStreamMethodEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LiveStreamMethodEnum.java new file mode 100644 index 0000000..b49cdc1 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/LiveStreamMethodEnum.java @@ -0,0 +1,29 @@ +package com.dji.sample.manage.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Getter +public enum LiveStreamMethodEnum { + + LIVE_START_PUSH("live_start_push"), + + LIVE_STOP_PUSH("live_stop_push"), + + LIVE_SET_QUALITY("live_set_quality"), + + LIVE_LENS_CHANGE("live_lens_change"), + + UNKNOWN("unknown"); + + private String method; + + LiveStreamMethodEnum(String method) { + this.method = method; + } + +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/LogsFileMethodEnum.java b/src/main/java/com/dji/sample/manage/model/enums/LogsFileMethodEnum.java new file mode 100644 index 0000000..42b1b87 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/LogsFileMethodEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Getter +public enum LogsFileMethodEnum { + + FILE_UPLOAD_LIST("fileupload_list"), + + FILE_UPLOAD_START("fileupload_start"), + + FILE_UPLOAD_UPDATE("fileupload_update"), + + UNKNOWN("unknown"); + + private String method; + + LogsFileMethodEnum(String method) { + this.method = method; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/StateSwitchReceiver.java b/src/main/java/com/dji/sample/manage/model/enums/StateSwitchReceiver.java new file mode 100644 index 0000000..27fb2a9 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/StateSwitchReceiver.java @@ -0,0 +1,30 @@ +package com.dji.sample.manage.model.enums; + +import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/28 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StateSwitchReceiver extends BasicDeviceProperty { + + public static final int DISABLE = 0; + + public static final int ENABLE = 1; + + private Integer value; + + @Override + public boolean valid() { + return Objects.nonNull(this.value) && (this.value == DISABLE || this.value == ENABLE); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java index bd69415..fa6924e 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/AlternateLandPointReceiver.java @@ -15,4 +15,6 @@ public class AlternateLandPointReceiver { private Double longitude; private Double safeLandHeight; + + private Integer isConfigured; } diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BackupBatteryReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/BackupBatteryReceiver.java new file mode 100644 index 0000000..257316d --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/BackupBatteryReceiver.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/3 + */ +@Data +public class BackupBatteryReceiver { + + private Integer voltage; + + private Float temperature; + + @JsonProperty("switch") + private Integer batterySwitch; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java b/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java new file mode 100644 index 0000000..b232ce8 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/27 + */ +@Data +public class BasicDeviceProperty { + + public boolean valid() { + return false; + } + + public boolean canPublish(String fieldName, OsdSubDeviceReceiver osd) { + return valid(); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java index 616b5a9..6c17c15 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/CapacityVideoReceiver.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Data; +import java.util.List; + /** * @author sean.zhou * @date 2021/11/18 @@ -16,4 +18,6 @@ public class CapacityVideoReceiver { private String videoIndex; private String videoType; + + private List switchableVideoTypes; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DeviceMaintainStatusReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DeviceMaintainStatusReceiver.java new file mode 100644 index 0000000..05e3ed4 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/DeviceMaintainStatusReceiver.java @@ -0,0 +1,16 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +import java.util.List; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/3 + */ +@Data +public class DeviceMaintainStatusReceiver { + + private List maintainStatusArray; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DistanceLimitStatusReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DistanceLimitStatusReceiver.java new file mode 100644 index 0000000..72129c7 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/DistanceLimitStatusReceiver.java @@ -0,0 +1,57 @@ +package com.dji.sample.manage.model.receiver; + +import com.dji.sample.manage.model.enums.StateSwitchReceiver; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * The state of the drone's limited distance + * @author sean + * @version 1.3 + * @date 2022/10/27 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DistanceLimitStatusReceiver extends BasicDeviceProperty { + + private Integer state; + + private Integer distanceLimit; + + private static final int DISTANCE_MAX = 8000; + + private static final int DISTANCE_MIN = 15; + + @Override + public boolean valid() { + boolean valid = Objects.nonNull(state) || Objects.nonNull(distanceLimit); + if (Objects.nonNull(state)) { + valid = new StateSwitchReceiver(this.state).valid(); + } + if (Objects.nonNull(distanceLimit)) { + valid &= distanceLimit >= DISTANCE_MIN && distanceLimit <= DISTANCE_MAX; + } + return valid; + } + + @Override + public boolean canPublish(String fieldName, OsdSubDeviceReceiver osd) { + DistanceLimitStatusReceiver distanceLimitStatus = osd.getDistanceLimitStatus(); + switch (fieldName) { + case "state": + return Objects.isNull(distanceLimitStatus.getState()) || + Objects.nonNull(distanceLimitStatus.getState()) && + distanceLimitStatus.getState().intValue() != this.state; + case "distance_limit": + return Objects.isNull(distanceLimitStatus.getDistanceLimit()) || + Objects.nonNull(distanceLimitStatus.getDistanceLimit()) && + distanceLimitStatus.getDistanceLimit().intValue() != this.distanceLimit; + default: + throw new RuntimeException("Property " + fieldName + " does not exist."); + } + } +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DockWirelessLinkReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DockWirelessLinkReceiver.java new file mode 100644 index 0000000..83bfb3f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/DockWirelessLinkReceiver.java @@ -0,0 +1,38 @@ +package com.dji.sample.manage.model.receiver; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/3 + */ +@Data +public class DockWirelessLinkReceiver { + + @JsonProperty("4g_freq_band") + private Float fourGFreqBand; + + @JsonProperty("4g_gnd_quality") + private Integer fourGGndQuality; + + @JsonProperty("4g_link_state") + private Integer fourGLinkState; + + @JsonProperty("4g_quality") + private Integer fourGQuality; + + @JsonProperty("4g_uav_quality") + private Integer fourGUavQuality; + + private Integer dongleNumber; + + private Integer linkWorkmode; + + private Float sdrFreqBand; + + private Integer sdrLinkState; + + private Integer sdrQuality; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DroneBatteryMaintenanceInfoReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DroneBatteryMaintenanceInfoReceiver.java new file mode 100644 index 0000000..1718aca --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/DroneBatteryMaintenanceInfoReceiver.java @@ -0,0 +1,16 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2022/11/3 + */ +@Data +public class DroneBatteryMaintenanceInfoReceiver { + + private Integer maintenanceState; + + private Long maintenanceTimeLeft; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/HeightLimitReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/HeightLimitReceiver.java new file mode 100644 index 0000000..7a07565 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/HeightLimitReceiver.java @@ -0,0 +1,29 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/28 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class HeightLimitReceiver extends BasicDeviceProperty { + + private static final int HEIGHT_LIMIT_MAX = 1500; + + private static final int HEIGHT_LIMIT_MIN = 20; + + private Integer value; + + @Override + public boolean valid() { + return Objects.nonNull(this.value) && this.value >= HEIGHT_LIMIT_MIN && this.value <= HEIGHT_LIMIT_MAX; + } +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/MaintainStatusReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/MaintainStatusReceiver.java new file mode 100644 index 0000000..7896f31 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/MaintainStatusReceiver.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/3 + */ +@Data +public class MaintainStatusReceiver { + + private Integer state; + + private Integer lastMaintainType; + + private Long lastMaintainTime; + + private Long lastMaintainWorkSorties; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java index 84698cc..2aac8ae 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/NetworkStateReceiver.java @@ -14,5 +14,5 @@ public class NetworkStateReceiver { private Integer quality; - private float rate; + private Float rate; } diff --git a/src/main/java/com/dji/sample/manage/model/receiver/ObstacleAvoidanceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/ObstacleAvoidanceReceiver.java new file mode 100644 index 0000000..e40a341 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/ObstacleAvoidanceReceiver.java @@ -0,0 +1,62 @@ +package com.dji.sample.manage.model.receiver; + +import com.dji.sample.manage.model.enums.StateSwitchReceiver; +import lombok.Data; + +import java.util.Objects; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/27 + */ +@Data +public class ObstacleAvoidanceReceiver extends BasicDeviceProperty { + + private Integer horizon; + + private Integer upside; + + private Integer downside; + + @Override + public boolean valid() { + boolean valid = Objects.nonNull(this.horizon) || Objects.nonNull(this.upside) || Objects.nonNull(this.downside); + + StateSwitchReceiver stateSwitch = new StateSwitchReceiver(); + if (Objects.nonNull(this.horizon)) { + stateSwitch.setValue(this.horizon); + valid = stateSwitch.valid(); + } + if (Objects.nonNull(this.upside)) { + stateSwitch.setValue(this.upside); + valid &= stateSwitch.valid(); + } + if (Objects.nonNull(this.downside)) { + stateSwitch.setValue(this.downside); + valid &= stateSwitch.valid(); + } + return valid; + } + + @Override + public boolean canPublish(String fieldName, OsdSubDeviceReceiver osd) { + ObstacleAvoidanceReceiver obstacleAvoidance = osd.getObstacleAvoidance(); + switch (fieldName) { + case "horizon": + return Objects.isNull(obstacleAvoidance.getHorizon()) || + Objects.nonNull(obstacleAvoidance.getHorizon()) && + obstacleAvoidance.getHorizon().intValue() != this.horizon; + case "upside": + return Objects.isNull(obstacleAvoidance.getUpside()) || + Objects.nonNull(obstacleAvoidance.getUpside()) && + obstacleAvoidance.getUpside().intValue() != this.upside; + case "downside": + return Objects.isNull(obstacleAvoidance.getDownside()) || + Objects.nonNull(obstacleAvoidance.getDownside()) && + obstacleAvoidance.getDownside().intValue() != this.downside; + default: + throw new RuntimeException("Property " + fieldName + " does not exist."); + } + } +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java index ebd8b34..0cd5196 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdDockReceiver.java @@ -4,8 +4,8 @@ import lombok.Data; /** * @author sean - * @version 1.0 - * @date 2022/5/11 + * @version 1.3 + * @date 2022/11/3 */ @Data public class OsdDockReceiver { @@ -22,8 +22,6 @@ public class OsdDockReceiver { private Float environmentTemperature; - private Integer environmentHumidity; - private Float temperature; private Integer humidity; @@ -36,32 +34,55 @@ public class OsdDockReceiver { private AlternateLandPointReceiver alternateLandPoint; - private Integer jobNumber; - - private Integer accTime; - private Long firstPowerOn; private PositionStateReceiver positionState; private StorageReceiver storage; + private Integer modeCode; + + private Integer coverState; + + private Integer supplementLightState; + + private Integer emergencyStopState; + + private Integer airConditionerMode; + + private Integer batteryStoreMode; + + private Integer alarmState; + + private Integer putterState; + + private DockSubDeviceReceiver subDevice; + + private Integer jobNumber; + + private Long accTime; + + private Long activationTime; + + private DeviceMaintainStatusReceiver maintainStatus; + private Integer electricSupplyVoltage; private Integer workingVoltage; private Integer workingCurrent; - private Integer backupBatteryVoltage; + private BackupBatteryReceiver backupBattery; - private Integer modeCode; + private DroneBatteryMaintenanceInfoReceiver droneBatteryMaintenanceInfo; - private Integer coverState; + private Integer flighttaskStepCode; - private Integer supplementLightState; + private Integer flighttaskPrepareCapacity; - private Integer putterState; + private DockMediaFileDetailReceiver mediaFileDetail; - private DockSubDeviceReceiver subDevice; + private DockSdrReceiver sdr; + private DockWirelessLinkReceiver wirelessLink; } diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdDockTransmissionReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdDockTransmissionReceiver.java deleted file mode 100644 index cdd9abf..0000000 --- a/src/main/java/com/dji/sample/manage/model/receiver/OsdDockTransmissionReceiver.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.dji.sample.manage.model.receiver; - -import lombok.Data; - -/** - * @author sean - * @version 1.1 - * @date 2022/6/17 - */ -@Data -public class OsdDockTransmissionReceiver { - - private Integer flighttaskStepCode; - - private DockMediaFileDetailReceiver mediaFileDetail; - - private DockSdrReceiver sdr; -} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java index 2cb54df..b39fb09 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdSubDeviceReceiver.java @@ -58,4 +58,13 @@ public class OsdSubDeviceReceiver { private List payloads; private StorageReceiver storage; + + private Integer nightLightsState; + + private Integer heightLimit; + + private DistanceLimitStatusReceiver distanceLimitStatus; + + private ObstacleAvoidanceReceiver obstacleAvoidance; + } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/RequestConfigReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/RequestConfigReceiver.java new file mode 100644 index 0000000..e468a6f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/RequestConfigReceiver.java @@ -0,0 +1,16 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Data +public class RequestConfigReceiver { + + private String configType; + + private String configScope; +} diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java b/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java index 201f82c..97b901b 100644 --- a/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java +++ b/src/main/java/com/dji/sample/manage/service/IDeviceDictionaryService.java @@ -13,10 +13,12 @@ public interface IDeviceDictionaryService { /** * Query the type data of the device based on domain, device type and sub type. + * + * @param domain * @param deviceType * @param subType * @return */ - Optional getOneDictionaryInfoByTypeSubType(Integer deviceType, Integer subType); + Optional getOneDictionaryInfoByTypeSubType(Integer domain, Integer deviceType, Integer subType); } diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceService.java b/src/main/java/com/dji/sample/manage/service/IDeviceService.java index 37e5a44..ff41435 100644 --- a/src/main/java/com/dji/sample/manage/service/IDeviceService.java +++ b/src/main/java/com/dji/sample/manage/service/IDeviceService.java @@ -8,13 +8,17 @@ import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO; import com.dji.sample.manage.model.dto.TopologyDeviceDTO; +import com.dji.sample.manage.model.enums.DeviceSetPropertyEnum; import com.dji.sample.manage.model.param.DeviceQueryParam; import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver; import com.dji.sample.manage.model.receiver.StatusGatewayReceiver; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -33,10 +37,10 @@ public interface IDeviceService { /** * The device goes offline. - * @param gatewaySn + * @param gateway * @return Whether the offline is successful. */ - Boolean deviceOffline(String gatewaySn); + Boolean deviceOffline(StatusGatewayReceiver gateway); /** * The aircraft goes offline. @@ -133,10 +137,9 @@ public interface IDeviceService { /** * Handle messages from the osd topic. - * @param topic osd - * @param payload + * @param message osd */ - void handleOSD(String topic, byte[] payload); + void handleOSD(Message message); /** * Update the device information. @@ -205,4 +208,28 @@ public interface IDeviceService { * @return */ ResponseResult createDeviceOtaJob(String workspaceId, List upgradeDTOS); + + /** + * Set the property parameters of the drone. + * @param workspaceId + * @param dockSn + * @param propertyEnum + * @param param + */ + void devicePropertySet(String workspaceId, String dockSn, DeviceSetPropertyEnum propertyEnum, JsonNode param); + + /** + * Set one property parameters of the drone. + * @param topic + * @param propertyEnum + * @param value + */ + void deviceOnePropertySet(String topic, DeviceSetPropertyEnum propertyEnum, Map.Entry value); + + /** + * Determine if the device is online. + * @param sn + * @return + */ + Boolean checkDeviceOnline(String sn); } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java b/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java index f2a2470..2fb7a4f 100644 --- a/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java +++ b/src/main/java/com/dji/sample/manage/service/ILiveStreamService.java @@ -48,4 +48,11 @@ public interface ILiveStreamService { * @return */ ResponseResult liveSetQuality(LiveTypeDTO liveParam); + + /** + * Switches the lens of the device during the live streaming. + * @param liveParam + * @return + */ + ResponseResult liveLensChange(LiveTypeDTO liveParam); } diff --git a/src/main/java/com/dji/sample/manage/service/IRequestsConfigService.java b/src/main/java/com/dji/sample/manage/service/IRequestsConfigService.java new file mode 100644 index 0000000..9f0222b --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IRequestsConfigService.java @@ -0,0 +1,15 @@ +package com.dji.sample.manage.service; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +public interface IRequestsConfigService { + + /** + * Get the parameters required by config method. + * @return + */ + Object getConfig(); +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java index ec769b3..bfe6de9 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/CameraVideoServiceImpl.java @@ -23,7 +23,8 @@ public class CameraVideoServiceImpl implements ICameraVideoService { if (receiver != null) { builder.id(UUID.randomUUID().toString()) .index(receiver.getVideoIndex()) - .type(receiver.getVideoType()); + .type(receiver.getVideoType()) + .switchVideoTypes(receiver.getSwitchableVideoTypes()); } return builder.build(); } diff --git a/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java index be5020d..13ddc9b 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/CapacityCameraServiceImpl.java @@ -5,6 +5,7 @@ import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.manage.model.dto.CapacityCameraDTO; import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.manage.model.receiver.CapacityCameraReceiver; import com.dji.sample.manage.service.ICameraVideoService; import com.dji.sample.manage.service.ICapacityCameraService; @@ -68,7 +69,7 @@ public class CapacityCameraServiceImpl implements ICapacityCameraService { // type-subType-index if (indexArr.length == 3) { Optional dictionaryOpt = dictionaryService - .getOneDictionaryInfoByTypeSubType(indexArr[0], indexArr[1]); + .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), indexArr[0], indexArr[1]); dictionaryOpt.ifPresent(dictionary -> builder.name(dictionary.getDeviceName())); } diff --git a/src/main/java/com/dji/sample/manage/service/impl/ConfigProductServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/ConfigProductServiceImpl.java new file mode 100644 index 0000000..b117586 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/ConfigProductServiceImpl.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.manage.model.common.NtpServerProperties; +import com.dji.sample.manage.model.dto.NtpServerDTO; +import com.dji.sample.manage.service.IRequestsConfigService; +import org.springframework.stereotype.Service; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Service +public class ConfigProductServiceImpl implements IRequestsConfigService { + + @Override + public Object getConfig() { + return new NtpServerDTO(NtpServerProperties.host); + } +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java index f0dbad0..a4574b7 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceDictionaryServiceImpl.java @@ -25,16 +25,18 @@ public class DeviceDictionaryServiceImpl implements IDeviceDictionaryService { private IDeviceDictionaryMapper mapper; @Override - public Optional getOneDictionaryInfoByTypeSubType(Integer deviceType, Integer subType) { - if (deviceType == null || subType == null) { + public Optional getOneDictionaryInfoByTypeSubType(Integer domain, Integer deviceType, Integer subType) { + if (domain == null || deviceType == null || subType == null) { return Optional.empty(); } return Optional.ofNullable( entityConvertToDTO( mapper.selectOne( new LambdaQueryWrapper() + .eq(DeviceDictionaryEntity::getDomain, domain) .eq(DeviceDictionaryEntity::getDeviceType, deviceType) - .eq(DeviceDictionaryEntity::getSubType, subType)))); + .eq(DeviceDictionaryEntity::getSubType, subType) + .last(" limit 1 ")))); } /** diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java index 3cf9777..7e8882c 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceFirmwareServiceImpl.java @@ -18,6 +18,7 @@ import com.dji.sample.manage.model.entity.DeviceFirmwareEntity; import com.dji.sample.manage.model.enums.UserTypeEnum; import com.dji.sample.manage.model.param.DeviceOtaCreateParam; import com.dji.sample.manage.service.IDeviceFirmwareService; +import com.dji.sample.manage.service.IDeviceService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -61,6 +62,9 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { @Autowired private IWebSocketManageService webSocketManageService; + @Autowired + private IDeviceService deviceService; + @Override public Optional getFirmware(String deviceName, String version) { return Optional.ofNullable(entity2Dto(mapper.selectOne( @@ -83,7 +87,7 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { public List getDeviceOtaFirmware(List upgradeDTOS) { List deviceOtaList = new ArrayList<>(); upgradeDTOS.forEach(upgradeDevice -> { - boolean exist = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + upgradeDevice.getSn()) > 0; + boolean exist = deviceService.checkDeviceOnline(upgradeDevice.getSn()); if (!exist) { throw new IllegalArgumentException("Device is offline."); } @@ -169,7 +173,7 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { .bid(receiver.getBid()) .method(receiver.getMethod()) .timestamp(System.currentTimeMillis()) - .data(ResponseResult.success()) + .data(RequestsReply.success()) .build()); } } diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java index eec4b41..b94a0ad 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceLogsServiceImpl.java @@ -16,10 +16,7 @@ import com.dji.sample.component.websocket.service.IWebSocketManageService; import com.dji.sample.manage.dao.IDeviceLogsMapper; import com.dji.sample.manage.model.dto.*; import com.dji.sample.manage.model.entity.DeviceLogsEntity; -import com.dji.sample.manage.model.enums.DeviceDomainEnum; -import com.dji.sample.manage.model.enums.DeviceLogsStatusEnum; -import com.dji.sample.manage.model.enums.LogsFileUpdateMethodEnum; -import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.model.enums.*; import com.dji.sample.manage.model.param.DeviceLogsCreateParam; import com.dji.sample.manage.model.param.DeviceLogsQueryParam; import com.dji.sample.manage.model.param.LogsFileUpdateParam; @@ -113,20 +110,17 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { } String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF; - Optional serviceReplyOpt = messageSenderService.publishWithReply( + LogsFileUploadList data = messageSenderService.publishWithReply( LogsFileUploadList.class, topic, CommonTopicResponse.builder() .tid(UUID.randomUUID().toString()) .bid(UUID.randomUUID().toString()) - .method(ServicesMethodEnum.FILE_UPLOAD_LIST.getMethod()) + .method(LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod()) .timestamp(System.currentTimeMillis()) .data(Map.of(MapKeyConst.MODULE_LIST, domainList)) .build(), 1); - if (serviceReplyOpt.isEmpty()) { - return ResponseResult.error("No message reply received."); - } - LogsFileUploadList data = serviceReplyOpt.get(); + for (LogsFileUpload file : data.getFiles()) { if (file.getDeviceSn().isBlank()) { file.setDeviceSn(deviceSn); @@ -170,20 +164,16 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { credentialsDTO.setParams(LogsFileUploadList.builder().files(files).build()); String bid = UUID.randomUUID().toString(); - Optional serviceReply = messageSenderService.publishWithReply( + ServiceReply reply = messageSenderService.publishWithReply( TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF, CommonTopicResponse.builder() .tid(UUID.randomUUID().toString()) .bid(bid) .timestamp(System.currentTimeMillis()) - .method(ServicesMethodEnum.FILE_UPLOAD_START.getMethod()) + .method(LogsFileMethodEnum.FILE_UPLOAD_START.getMethod()) .data(credentialsDTO) .build()); - if (serviceReply.isEmpty()) { - return ResponseResult.error("No message reply received."); - } - ServiceReply reply = serviceReply.get(); if (ResponseResult.CODE_SUCCESS != reply.getResult()) { return ResponseResult.error(String.valueOf(reply.getResult())); } @@ -207,19 +197,15 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { } String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF; String bid = UUID.randomUUID().toString(); - Optional serviceReply = messageSenderService.publishWithReply(topic, + ServiceReply reply = messageSenderService.publishWithReply(topic, CommonTopicResponse.builder() .tid(UUID.randomUUID().toString()) .bid(bid) .timestamp(System.currentTimeMillis()) - .method(ServicesMethodEnum.FILE_UPLOAD_UPDATE.getMethod()) + .method(LogsFileMethodEnum.FILE_UPLOAD_UPDATE.getMethod()) .data(param) .build()); - if (serviceReply.isEmpty()) { - return ResponseResult.error("No message reply received."); - } - ServiceReply reply = serviceReply.get(); if (ResponseResult.CODE_SUCCESS != reply.getResult()) { return ResponseResult.error("Error Code : " + reply.getResult()); } @@ -249,7 +235,7 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { .bid(receiver.getBid()) .method(receiver.getMethod()) .timestamp(System.currentTimeMillis()) - .data(ResponseResult.success()) + .data(RequestsReply.success()) .build()); } @@ -284,7 +270,6 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { List fileReceivers = output.getExt().getFiles(); if (CollectionUtils.isEmpty(fileReceivers)) { redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); - return; } // refresh cache. diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java index 9fc690d..16ed4be 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceOSDServiceImpl.java @@ -1,6 +1,7 @@ package com.dji.sample.manage.service.impl; import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; import com.dji.sample.component.websocket.model.BizCodeEnum; import com.dji.sample.component.websocket.model.CustomWebSocketMessage; @@ -74,7 +75,7 @@ public class DeviceOSDServiceImpl extends AbstractTSAService { log.warn("Please remount the payload, or restart the drone. Otherwise the data of the payload will not be received."); } - + redisOps.setWithExpire(RedisConst.OSD_PREFIX + device.getDeviceSn(), data, RedisConst.DEVICE_ALIVE_SECOND); wsMessage.getData().setHost(data); sendMessageService.sendBatch(webSessions, wsMessage); diff --git a/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java index 123660b..1e6f52b 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DevicePayloadServiceImpl.java @@ -9,6 +9,7 @@ import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; import com.dji.sample.manage.model.dto.DevicePayloadDTO; import com.dji.sample.manage.model.entity.DevicePayloadEntity; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver; import com.dji.sample.manage.service.ICapacityCameraService; @@ -204,7 +205,7 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { if (arr.length == 3) { Optional dictionaryOpt = dictionaryService - .getOneDictionaryInfoByTypeSubType(arr[0], arr[1]); + .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), arr[0], arr[1]); dictionaryOpt.ifPresent(dictionary -> builder.payloadName(dictionary.getDeviceName()) .payloadDesc(dictionary.getDeviceDesc())); diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java index 1fecf85..75a97d1 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceServiceImpl.java @@ -20,21 +20,20 @@ import com.dji.sample.component.websocket.service.IWebSocketManageService; import com.dji.sample.manage.dao.IDeviceMapper; import com.dji.sample.manage.model.dto.*; import com.dji.sample.manage.model.entity.DeviceEntity; -import com.dji.sample.manage.model.enums.DeviceDomainEnum; -import com.dji.sample.manage.model.enums.DeviceFirmwareStatusEnum; -import com.dji.sample.manage.model.enums.IconUrlEnum; -import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.model.enums.*; import com.dji.sample.manage.model.param.DeviceOtaCreateParam; import com.dji.sample.manage.model.param.DeviceQueryParam; import com.dji.sample.manage.model.receiver.*; import com.dji.sample.manage.service.*; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -99,8 +98,12 @@ public class DeviceServiceImpl implements IDeviceService { @Qualifier("gatewayOSDServiceImpl") private ITSAService tsaService; + private static final List INIT_TOPICS_SUFFIX = List.of( + OSD_SUF, STATE_SUF, SERVICES_SUF + _REPLY_SUF, REQUESTS_SUF, EVENTS_SUF, PROPERTY_SUF + SET_SUF + _REPLY_SUF); + @Override - public Boolean deviceOffline(String gatewaySn) { + public Boolean deviceOffline(StatusGatewayReceiver gateway) { + String gatewaySn = gateway.getSn(); this.subscribeTopicOnline(gatewaySn); // Only the remote controller is logged in and the aircraft is not connected. @@ -111,26 +114,21 @@ public class DeviceServiceImpl implements IDeviceService { Optional gatewayOpt = this.getDeviceBySn(gatewaySn); if (gatewayOpt.isPresent()) { DeviceDTO value = gatewayOpt.get(); - value.setChildDeviceSn(value.getDeviceSn()); value.setBoundTime(null); value.setLoginTime(null); redisOps.setWithExpire(key, value, RedisConst.DEVICE_ALIVE_SECOND); this.pushDeviceOnlineTopo(value.getWorkspaceId(), gatewaySn, gatewaySn); return true; } - DeviceDTO gateway = DeviceDTO.builder() - .deviceSn(gatewaySn) - .childDeviceSn(gatewaySn) - .domain(DeviceDomainEnum.GATEWAY.getDesc()) - .build(); - gatewayOpt.map(DeviceDTO::getWorkspaceId).ifPresent(gateway::setWorkspaceId); - redisOps.setWithExpire(key, gateway, RedisConst.DEVICE_ALIVE_SECOND); - this.pushDeviceOnlineTopo(gateway.getWorkspaceId(), gatewaySn, gatewaySn); - return true; + + // When connecting for the first time + DeviceEntity gatewayDevice = deviceGatewayConvertToDeviceEntity(gateway); + return firstSaveDevice(gatewayDevice, null); } - String deviceSn = ((DeviceDTO)(redisOps.get(key))).getChildDeviceSn(); - if (deviceSn.equals(gatewaySn)) { + DeviceDTO deviceDTO = (DeviceDTO) (redisOps.get(key)); + String deviceSn = deviceDTO.getChildDeviceSn(); + if (!StringUtils.hasText(deviceSn)) { return true; } @@ -139,21 +137,23 @@ public class DeviceServiceImpl implements IDeviceService { @Override public Boolean subDeviceOffline(String deviceSn) { - // Cancel drone-related subscriptions. - this.unsubscribeTopicOffline(deviceSn); - payloadService.deletePayloadsByDeviceSn(new ArrayList<>(List.of(deviceSn))); - // If no information about this gateway device exists in the database, the drone is considered to be offline. + // If no information about this device exists in the cache, the drone is considered to be offline. String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn; if (!redisOps.checkExist(key) || redisOps.getExpire(key) <= 0) { log.debug("The drone is already offline."); return true; } DeviceDTO device = (DeviceDTO) redisOps.get(key); + // Cancel drone-related subscriptions. + this.unsubscribeTopicOffline(deviceSn); + + payloadService.deletePayloadsByDeviceSn(new ArrayList<>(List.of(deviceSn))); // Publish the latest device topology information in the current workspace. this.pushDeviceOfflineTopo(device.getWorkspaceId(), deviceSn); redisOps.del(key); + redisOps.del(RedisConst.OSD_PREFIX + device.getDeviceSn()); log.debug("{} offline.", deviceSn); return true; } @@ -165,7 +165,6 @@ public class DeviceServiceImpl implements IDeviceService { // change log: Use redis instead of long time = redisOps.getExpire(key); long gatewayTime = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn()); - long now = System.currentTimeMillis(); if (time > 0 && gatewayTime > 0) { redisOps.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND); @@ -197,61 +196,37 @@ public class DeviceServiceImpl implements IDeviceService { this.updateDevice(gateway); }); - DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway); - gateway.setChildSn(deviceSn); - // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module. - gateway.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl()); - // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module. - gateway.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl()); - gateway.setLoginTime(now); - DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0)); - // Set the icon of the drone device displayed in the pilot's map when it is selected, required in the TSA module. - subDevice.setUrlNormal(IconUrlEnum.NORMAL_EQUIPMENT.getUrl()); - // Set the icon of the drone device displayed in the pilot's map, required in the TSA module. - subDevice.setUrlSelect(IconUrlEnum.SELECT_EQUIPMENT.getUrl()); - subDevice.setLoginTime(now); + boolean isSave = firstSaveDevice(gateway, deviceSn) && firstSaveDevice(subDevice, null); + if (!isSave) { + return false; + } // dock go online if (deviceGateway.getDomain() != null && DeviceDomainEnum.DOCK.getVal() == deviceGateway.getDomain()) { Optional deviceOpt = this.getDeviceBySn(deviceGateway.getSn()); if (deviceOpt.isEmpty()) { - log.info("The dock is not bound and cannot go online."); + log.info("The dock is not bound and cannot go online. Please refer to the Cloud API document video for binding."); return false; } gateway.setNickname(null); subDevice.setNickname(null); } - Optional gatewayOpt = this.saveDevice(gateway); - String workspaceId = this.saveDevice(subDevice).orElse(subDevice).getWorkspaceId(); + String workspaceId = subDevice.getWorkspaceId(); - redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceSn, - DeviceDTO.builder() - .deviceSn(deviceSn) - .domain(DeviceDomainEnum.SUB_DEVICE.getDesc()) - .workspaceId(workspaceId) - .build(), - RedisConst.DEVICE_ALIVE_SECOND); - redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn(), - DeviceDTO.builder() - .deviceSn(gateway.getDeviceSn()) - .workspaceId(gatewayOpt.orElse(gateway).getWorkspaceId()) - .childDeviceSn(deviceSn) - .domain(deviceGateway.getDomain() != null ? - DeviceDomainEnum.getDesc(deviceGateway.getDomain()) : - DeviceDomainEnum.GATEWAY.getDesc()) - .build(), - RedisConst.DEVICE_ALIVE_SECOND); - log.debug("{} online.", subDevice.getDeviceSn()); - - if (StringUtils.hasText(workspaceId)) { - this.pushDeviceOnlineTopo(workspaceId, deviceGateway.getSn(), deviceSn); + this.subscribeTopicOnline(deviceGateway.getSn()); + if (!StringUtils.hasText(workspaceId)) { + log.info("The drone is not bound and cannot go online. Please refer to the Cloud API document video for binding."); + return true; } + // Subscribe to topic related to drone devices. this.subscribeTopicOnline(deviceSn); - this.subscribeTopicOnline(deviceGateway.getSn()); + this.pushDeviceOnlineTopo(workspaceId, deviceGateway.getSn(), deviceSn); + + log.debug("{} online.", subDevice.getDeviceSn()); return true; } @@ -264,20 +239,14 @@ public class DeviceServiceImpl implements IDeviceService { return; } } - topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF); - topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF); - topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF); - topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + REQUESTS_SUF); - topicService.subscribe(THING_MODEL_PRE + PRODUCT + sn + EVENTS_SUF); + String prefix = THING_MODEL_PRE + PRODUCT + sn; + INIT_TOPICS_SUFFIX.forEach(suffix -> topicService.subscribe(prefix + suffix)); } @Override public void unsubscribeTopicOffline(String sn) { - topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + OSD_SUF); - topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + STATE_SUF); - topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + SERVICES_SUF + _REPLY_SUF); - topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + REQUESTS_SUF); - topicService.unsubscribe(THING_MODEL_PRE + PRODUCT + sn + EVENTS_SUF); + String prefix = THING_MODEL_PRE + PRODUCT + sn; + INIT_TOPICS_SUFFIX.forEach(suffix -> topicService.unsubscribe(prefix + suffix)); } @Override @@ -459,7 +428,10 @@ public class DeviceServiceImpl implements IDeviceService { } @Override - public void handleOSD(String topic, byte[] payload) { + @ServiceActivator(inputChannel = ChannelName.INBOUND_OSD) + public void handleOSD(Message message) { + String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); + byte[] payload = (byte[])message.getPayload(); CommonTopicReceiver receiver; try { String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(), @@ -568,7 +540,9 @@ public class DeviceServiceImpl implements IDeviceService { // Query the model information of this gateway device. Optional dictionaryOpt = dictionaryService - .getOneDictionaryInfoByTypeSubType(gateway.getType(), gateway.getSubType()); + .getOneDictionaryInfoByTypeSubType(Objects.nonNull(gateway.getDomain()) ? + gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal(), + gateway.getType(), gateway.getSubType()); dictionaryOpt.ifPresent(entity -> builder.deviceName(entity.getDeviceName()) @@ -598,7 +572,7 @@ public class DeviceServiceImpl implements IDeviceService { // Query the model information of this drone device. Optional dictionaryOpt = dictionaryService - .getOneDictionaryInfoByTypeSubType(device.getType(), device.getSubType()); + .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.SUB_DEVICE.getVal(), device.getType(), device.getSubType()); dictionaryOpt.ifPresent(dictionary -> builder.deviceName(dictionary.getDeviceName()) @@ -611,8 +585,7 @@ public class DeviceServiceImpl implements IDeviceService { .subType(device.getSubType()) .version(device.getVersion()) .deviceIndex(device.getIndex()) - .domain(device.getDomain() != null ? - device.getDomain() : DeviceDomainEnum.SUB_DEVICE.getVal()) + .domain(DeviceDomainEnum.SUB_DEVICE.getVal()) .build(); } @@ -690,15 +663,15 @@ public class DeviceServiceImpl implements IDeviceService { device.setBoundTime(LocalDateTime.now()); boolean isUpd = this.saveDevice(this.deviceDTO2Entity(device)).isPresent(); - if (DeviceDomainEnum.DOCK.getDesc().equals(device.getDomain())) { - return isUpd; - } if (!isUpd) { return false; } String key = RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(); DeviceDTO redisDevice = (DeviceDTO)redisOps.get(key); + if (Objects.isNull(redisDevice)) { + return false; + } redisDevice.setWorkspaceId(device.getWorkspaceId()); redisOps.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND); @@ -767,8 +740,8 @@ public class DeviceServiceImpl implements IDeviceService { assert dock != null; - Optional dockEntityOpt = this.bindDevice2Entity(dock); - Optional droneEntityOpt = this.bindDevice2Entity(drone); + Optional dockEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.DOCK.getVal(), dock); + Optional droneEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.SUB_DEVICE.getVal(), drone); List bindResult = new ArrayList<>(); @@ -855,7 +828,13 @@ public class DeviceServiceImpl implements IDeviceService { } @Override + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_FIRMWARE_VERSION) public void updateFirmwareVersion(FirmwareVersionReceiver receiver) { + // If the reported version is empty, it will not be processed to prevent misleading page. + if (!StringUtils.hasText(receiver.getFirmwareVersion())) { + return; + } + if (receiver.getDomain() == DeviceDomainEnum.SUB_DEVICE) { final DeviceDTO device = DeviceDTO.builder() .deviceSn(receiver.getSn()) @@ -885,31 +864,99 @@ public class DeviceServiceImpl implements IDeviceService { // The bids in the progress messages reported subsequently are the same. String bid = UUID.randomUUID().toString(); - Optional serviceReplyOpt = messageSender.publishWithReply( + ServiceReply serviceReply = messageSender.publishWithReply( topic, CommonTopicResponse.>>builder() .tid(UUID.randomUUID().toString()) .bid(bid) .timestamp(System.currentTimeMillis()) - .method(ServicesMethodEnum.OTA_CREATE.getMethod()) + .method(FirmwareMethodEnum.OTA_CREATE.getMethod()) .data(Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares)) .build()); - if (serviceReplyOpt.isEmpty()) { - return ResponseResult.error("No message reply received."); - } - ServiceReply serviceReply = serviceReplyOpt.get(); if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) { return ResponseResult.error(serviceReply.getResult(), "Firmware Error Code: " + serviceReply.getResult()); } - if (ServicesMethodEnum.OTA_CREATE.getProgress()) { - // Record the device state that needs to be updated. - deviceOtaFirmwares.forEach(deviceOta -> redisOps.setWithExpire( - RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(), - bid, - RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND)); - } + + // Record the device state that needs to be updated. + deviceOtaFirmwares.forEach(deviceOta -> redisOps.setWithExpire( + RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(), + bid, + RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND)); return ResponseResult.success(); } + @Override + public void devicePropertySet(String workspaceId, String dockSn, DeviceSetPropertyEnum propertyEnum, JsonNode param) { + boolean dockOnline = this.checkDeviceOnline(dockSn); + if (!dockOnline) { + throw new RuntimeException("Dock is offline."); + } + DeviceDTO deviceDTO = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn); + boolean deviceOnline = this.checkDeviceOnline(deviceDTO.getChildDeviceSn()); + if (!deviceOnline) { + throw new RuntimeException("Device is offline."); + } + + // Make sure the data is valid. + BasicDeviceProperty basicDeviceProperty = objectMapper.convertValue(param, propertyEnum.getClazz()); + boolean valid = basicDeviceProperty.valid(); + if (!valid) { + throw new IllegalArgumentException(CommonErrorEnum.ILLEGAL_ARGUMENT.getErrorMsg()); + } + + String topic = THING_MODEL_PRE + PRODUCT + dockSn + PROPERTY_SUF + SET_SUF; + OsdSubDeviceReceiver osd = (OsdSubDeviceReceiver) redisOps.get(RedisConst.OSD_PREFIX + deviceDTO.getChildDeviceSn()); + if (!param.isObject()) { + this.deviceOnePropertySet(topic, propertyEnum, Map.entry(propertyEnum.getProperty(), param)); + return; + } + // If there are multiple parameters, set them separately. + for (Iterator> filed = param.fields(); filed.hasNext(); ) { + Map.Entry node = filed.next(); + boolean isPublish = basicDeviceProperty.canPublish(node.getKey(), osd); + if (!isPublish) { + continue; + } + this.deviceOnePropertySet(topic, propertyEnum, Map.entry(propertyEnum.getProperty(), node)); + } + + } + + @Override + public void deviceOnePropertySet(String topic, DeviceSetPropertyEnum propertyEnum, Map.Entry value) { + if (Objects.isNull(value) || Objects.isNull(value.getValue())) { + throw new IllegalArgumentException(CommonErrorEnum.ILLEGAL_ARGUMENT.getErrorMsg()); + } + + Map reply = messageSender.publishWithReply( + Map.class, topic, + CommonTopicResponse.builder() + .bid(UUID.randomUUID().toString()) + .tid(UUID.randomUUID().toString()) + .timestamp(System.currentTimeMillis()) + .data(value) + .build(), + 2); + + while (true) { + reply = (Map) reply.get(value.getKey()); + if (value.getValue() instanceof JsonNode) { + break; + } + value = (Map.Entry) value.getValue(); + } + + SetReply setReply = objectMapper.convertValue(reply, SetReply.class); + if (SetReplyStatusResultEnum.SUCCESS.getVal() != setReply.getResult()) { + throw new RuntimeException("Failed to set " + value.getKey() + "; Error Code: " + setReply.getResult()); + } + + } + + public Boolean checkDeviceOnline(String sn) { + String key = RedisConst.DEVICE_ONLINE_PREFIX + sn; + return redisOps.checkExist(key) && redisOps.getExpire(key) > 0; + } + /** * Convert device data transfer object into database entity object. * @param dto @@ -940,15 +987,17 @@ public class DeviceServiceImpl implements IDeviceService { /** * Convert device binding data object into database entity object. + * + * @param domain * @param receiver * @return */ - private Optional bindDevice2Entity(BindDeviceReceiver receiver) { + private Optional bindDevice2Entity(Integer domain, BindDeviceReceiver receiver) { if (receiver == null) { return Optional.empty(); } int[] droneKey = Arrays.stream(receiver.getDeviceModelKey().split("-")).mapToInt(Integer::parseInt).toArray(); - Optional dictionaryOpt = dictionaryService.getOneDictionaryInfoByTypeSubType(droneKey[1], droneKey[2]); + Optional dictionaryOpt = dictionaryService.getOneDictionaryInfoByTypeSubType(domain, droneKey[1], droneKey[2]); DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder(); dictionaryOpt.ifPresent(entity -> @@ -993,4 +1042,36 @@ public class DeviceServiceImpl implements IDeviceService { .organizationName(device.getWorkspaceName()) .build(); } + + private Boolean firstSaveDevice(DeviceEntity device, String deviceSn) { + + Optional deviceOpt = this.getDeviceBySn(device.getDeviceSn()); + if (deviceOpt.isEmpty()) { + // Set the icon of the gateway device displayed in the pilot's map, required in the TSA module. + device.setUrlNormal(IconUrlEnum.NORMAL_PERSON.getUrl()); + // Set the icon of the gateway device displayed in the pilot's map when it is selected, required in the TSA module. + device.setUrlSelect(IconUrlEnum.SELECT_PERSON.getUrl()); + } + device.setChildSn(deviceSn); + device.setLoginTime(System.currentTimeMillis()); + + Optional saveDeviceOpt = this.saveDevice(device); + if (saveDeviceOpt.isEmpty()) { + return false; + } + device.setWorkspaceId(saveDeviceOpt.get().getWorkspaceId()); + + redisOps.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), + DeviceDTO.builder() + .deviceSn(device.getDeviceSn()) + .workspaceId(device.getWorkspaceId()) + .childDeviceSn(deviceSn) + .domain(DeviceDomainEnum.getDesc(device.getDomain())) + .type(device.getDeviceType()) + .subType(device.getSubType()) + .build(), + RedisConst.DEVICE_ALIVE_SECOND); + + return true; + } } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java index 4c34cd5..f0bac72 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DockOSDServiceImpl.java @@ -8,7 +8,6 @@ import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.dto.TelemetryDTO; import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.manage.model.receiver.OsdDockReceiver; -import com.dji.sample.manage.model.receiver.OsdDockTransmissionReceiver; import org.springframework.stereotype.Service; import java.util.Collection; @@ -40,10 +39,6 @@ public class DockOSDServiceImpl extends AbstractTSAService { wsMessage.setBizCode(BizCodeEnum.DOCK_OSD.getCode()); OsdDockReceiver data = mapper.convertValue(receiver.getData(), OsdDockReceiver.class); wsMessage.getData().setHost(data); - if (data.getSubDevice() == null) { - OsdDockTransmissionReceiver transmission = mapper.convertValue(receiver.getData(), OsdDockTransmissionReceiver.class); - wsMessage.getData().setHost(transmission); - } sendMessageService.sendBatch(webSessions, wsMessage); } } diff --git a/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java index e5b6c8f..9aae21e 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/LiveStreamServiceImpl.java @@ -4,13 +4,13 @@ import com.dji.sample.common.error.LiveErrorEnum; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.component.mqtt.model.CommonTopicResponse; import com.dji.sample.component.mqtt.model.ServiceReply; -import com.dji.sample.component.mqtt.model.ServicesMethodEnum; import com.dji.sample.component.mqtt.model.StateDataEnum; import com.dji.sample.component.mqtt.service.IMessageSenderService; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.manage.model.dto.*; import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.enums.LiveStreamMethodEnum; import com.dji.sample.manage.model.enums.LiveUrlTypeEnum; import com.dji.sample.manage.model.enums.LiveVideoQualityEnum; import com.dji.sample.manage.model.param.DeviceQueryParam; @@ -70,7 +70,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { return devicesList.stream() .filter(device -> redisOps.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn())) .map(device -> CapacityDeviceDTO.builder() - .name(device.getDeviceName()) + .name(Objects.requireNonNullElse(device.getNickname(), device.getDeviceName())) .sn(device.getDeviceSn()) .camerasList(capacityCameraService.getCapacityCameraByDeviceSn(device.getDeviceSn())) .build()) @@ -96,7 +96,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { public ResponseResult liveStart(LiveTypeDTO liveParam) { // Check if this lens is available live. ResponseResult responseResult = this.checkBeforeLive(liveParam.getVideoId()); - if (responseResult.getCode() != 0) { + if (ResponseResult.CODE_SUCCESS != responseResult.getCode()) { return responseResult; } @@ -104,13 +104,10 @@ public class LiveStreamServiceImpl implements ILiveStreamService { // target topic String respTopic = THING_MODEL_PRE + PRODUCT + data.getDeviceSn() + SERVICES_SUF; - Optional receiveReplyOpt = this.publishLiveStart(respTopic, liveParam); + ServiceReply receiveReply = this.publishLiveStart(respTopic, liveParam); - if (receiveReplyOpt.isEmpty()) { - return ResponseResult.error(LiveErrorEnum.NO_REPLY); - } - if (receiveReplyOpt.get().getResult() != 0) { - return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult())); + if (ResponseResult.CODE_SUCCESS != receiveReply.getResult()) { + return ResponseResult.error(LiveErrorEnum.find(receiveReply.getResult())); } LiveUrlTypeEnum urlType = LiveUrlTypeEnum.find(liveParam.getUrlType()); @@ -132,7 +129,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { .toString()); break; case RTSP: - String url = receiveReplyOpt.get().getInfo().toString(); + String url = receiveReply.getInfo().toString(); this.resolveUrlUser(url, live); break; case UNKNOWN: @@ -151,12 +148,9 @@ public class LiveStreamServiceImpl implements ILiveStreamService { String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().getDeviceSn() + SERVICES_SUF; - Optional receiveReplyOpt = this.publishLiveStop(respTopic, videoId); - if (receiveReplyOpt.isEmpty()) { - return ResponseResult.error(LiveErrorEnum.NO_REPLY); - } - if (receiveReplyOpt.get().getResult() != 0) { - return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult())); + ServiceReply receiveReply = this.publishLiveStop(respTopic, videoId); + if (receiveReply.getResult() != 0) { + return ResponseResult.error(LiveErrorEnum.find(receiveReply.getResult())); } return ResponseResult.success(); @@ -177,23 +171,58 @@ public class LiveStreamServiceImpl implements ILiveStreamService { String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().getDeviceSn() + SERVICES_SUF; - Optional receiveReplyOpt = this.publishLiveSetQuality(respTopic, liveParam); - if (receiveReplyOpt.isEmpty()) { - return ResponseResult.error(LiveErrorEnum.NO_REPLY); + ServiceReply receiveReply = this.publishLiveSetQuality(respTopic, liveParam); + if (ResponseResult.CODE_SUCCESS == receiveReply.getResult()) { + return ResponseResult.error(LiveErrorEnum.find(receiveReply.getResult())); } - if (receiveReplyOpt.get().getResult() != 0) { - return ResponseResult.error(LiveErrorEnum.find(receiveReplyOpt.get().getResult())); + + return ResponseResult.success(); + } + + @Override + public ResponseResult liveLensChange(LiveTypeDTO liveParam) { + if (!StringUtils.hasText(liveParam.getVideoType())) { + return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS); + } + + ResponseResult responseResult = this.checkBeforeLive(liveParam.getVideoId()); + if (ResponseResult.CODE_SUCCESS != responseResult.getCode()) { + return responseResult; + } + if (DeviceDomainEnum.GATEWAY.getDesc().equals(responseResult.getData().getDomain())) { + return ResponseResult.error(LiveErrorEnum.FUNCTION_NOT_SUPPORT); + } + + String respTopic = THING_MODEL_PRE + PRODUCT + responseResult.getData().getDeviceSn() + SERVICES_SUF; + + ServiceReply receiveReply = this.publishLiveLensChange(respTopic, liveParam); + + if (ResponseResult.CODE_SUCCESS != receiveReply.getResult()) { + return ResponseResult.error(LiveErrorEnum.find(receiveReply.getResult())); } return ResponseResult.success(); } + private ServiceReply publishLiveLensChange(String respTopic, LiveTypeDTO liveParam) { + CommonTopicResponse response = new CommonTopicResponse<>(); + response.setTid(UUID.randomUUID().toString()); + response.setBid(UUID.randomUUID().toString()); + response.setMethod(LiveStreamMethodEnum.LIVE_LENS_CHANGE.getMethod()); + response.setData(liveParam); + + return messageSender.publishWithReply(respTopic, response); + } + /** * Check if this lens is available live. * @param videoId * @return */ private ResponseResult checkBeforeLive(String videoId) { + if (!StringUtils.hasText(videoId)) { + return ResponseResult.error(LiveErrorEnum.ERROR_PARAMETERS); + } String[] videoIdArr = videoId.split("/"); // drone sn / enumeration value of the location where the payload is mounted / payload lens if (videoIdArr.length != 3) { @@ -272,12 +301,12 @@ public class LiveStreamServiceImpl implements ILiveStreamService { * @param liveParam * @return */ - private Optional publishLiveStart(String topic, LiveTypeDTO liveParam) { + private ServiceReply publishLiveStart(String topic, LiveTypeDTO liveParam) { CommonTopicResponse response = new CommonTopicResponse<>(); response.setTid(UUID.randomUUID().toString()); response.setBid(UUID.randomUUID().toString()); response.setData(liveParam); - response.setMethod(ServicesMethodEnum.LIVE_START_PUSH.getMethod()); + response.setMethod(LiveStreamMethodEnum.LIVE_START_PUSH.getMethod()); return messageSender.publishWithReply(topic, response); } @@ -288,14 +317,14 @@ public class LiveStreamServiceImpl implements ILiveStreamService { * @param liveParam * @return */ - private Optional publishLiveSetQuality(String respTopic, LiveTypeDTO liveParam) { + private ServiceReply publishLiveSetQuality(String respTopic, LiveTypeDTO liveParam) { Map data = new ConcurrentHashMap<>(Map.of( "video_id", liveParam.getVideoId(), "video_quality", liveParam.getVideoQuality())); CommonTopicResponse> response = new CommonTopicResponse<>(); response.setTid(UUID.randomUUID().toString()); response.setBid(UUID.randomUUID().toString()); - response.setMethod(ServicesMethodEnum.LIVE_SET_QUALITY.getMethod()); + response.setMethod(LiveStreamMethodEnum.LIVE_SET_QUALITY.getMethod()); response.setData(data); return messageSender.publishWithReply(respTopic, response); @@ -307,13 +336,13 @@ public class LiveStreamServiceImpl implements ILiveStreamService { * @param videoId * @return */ - private Optional publishLiveStop(String topic, String videoId) { + private ServiceReply publishLiveStop(String topic, String videoId) { Map data = new ConcurrentHashMap<>(Map.of("video_id", videoId)); CommonTopicResponse> response = new CommonTopicResponse<>(); response.setTid(UUID.randomUUID().toString()); response.setBid(UUID.randomUUID().toString()); response.setData(data); - response.setMethod(ServicesMethodEnum.LIVE_STOP_PUSH.getMethod()); + response.setMethod(LiveStreamMethodEnum.LIVE_STOP_PUSH.getMethod()); return messageSender.publishWithReply(topic, response); } diff --git a/src/main/java/com/dji/sample/manage/service/impl/LogsFileIndexServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/LogsFileIndexServiceImpl.java index e773828..5eeb5a1 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/LogsFileIndexServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/LogsFileIndexServiceImpl.java @@ -50,7 +50,7 @@ public class LogsFileIndexServiceImpl implements ILogsFileIndexService { files.forEach(file -> { Optional fileOpt = this.getFileIndexByFileId(file.getFileId()); fileOpt.ifPresent(fileUpload -> { - fileUpload.setObjectKey(file.getStatus() ? file.getObjectKey() : ""); + fileUpload.setObjectKey(file.getStatus() ? file.getObjectKey() : null); list.add(fileUpload); }); }); diff --git a/src/main/java/com/dji/sample/manage/service/impl/RequestConfigContext.java b/src/main/java/com/dji/sample/manage/service/impl/RequestConfigContext.java new file mode 100644 index 0000000..5665145 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/RequestConfigContext.java @@ -0,0 +1,56 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.common.util.SpringBeanUtils; +import com.dji.sample.component.mqtt.model.*; +import com.dji.sample.component.mqtt.service.IMessageSenderService; +import com.dji.sample.manage.model.receiver.RequestConfigReceiver; +import com.dji.sample.manage.service.IRequestsConfigService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.MessageHeaders; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/10 + */ +@Service +public class RequestConfigContext { + + @Autowired + private IMessageSenderService messageSenderService; + + @Autowired + private ObjectMapper objectMapper; + + + /** + * Handles the config method of the requests topic. + * @param receiver + * @param headers + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_CONFIG, outputChannel = ChannelName.OUTBOUND) + void handleConfig(CommonTopicReceiver receiver, MessageHeaders headers) { + RequestConfigReceiver configReceiver = objectMapper.convertValue(receiver.getData(), RequestConfigReceiver.class); + Optional scopeEnumOpt = ConfigScopeEnum.find(configReceiver.getConfigScope()); + String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF; + CommonTopicResponse.CommonTopicResponseBuilder build = CommonTopicResponse.builder() + .tid(receiver.getTid()) + .bid(receiver.getBid()) + .timestamp(System.currentTimeMillis()) + .method(receiver.getMethod()); + if (scopeEnumOpt.isEmpty()) { + messageSenderService.publish(topic, build.build()); + return; + } + + IRequestsConfigService requestsConfigService = SpringBeanUtils.getBean(scopeEnumOpt.get().getClazz()); + build.data(requestsConfigService.getConfig()); + messageSenderService.publish(topic, build.build()); + } +} diff --git a/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java index 8f3df63..746fe99 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/UserServiceImpl.java @@ -1,6 +1,8 @@ package com.dji.sample.manage.service.impl; import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.DecodedJWT; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -29,6 +31,7 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -115,10 +118,22 @@ public class UserServiceImpl implements IUserService { if (!StringUtils.hasText(token)) { return Optional.empty(); } - CustomClaim customClaim = new CustomClaim(JWT.decode(token).getClaims()); + CustomClaim customClaim; + try { + DecodedJWT jwt = JwtUtil.verifyToken(token); + customClaim = new CustomClaim(jwt.getClaims()); + } catch (TokenExpiredException e) { + customClaim = new CustomClaim(JWT.decode(token).getClaims()); + } catch (Exception e) { + e.printStackTrace(); + return Optional.empty(); + } String refreshToken = JwtUtil.createToken(customClaim.convertToMap()); UserDTO user = entityConvertToDTO(this.getUserByUsername(customClaim.getUsername())); + if (Objects.isNull(user)) { + return Optional.empty(); + } user.setWorkspaceId(customClaim.getWorkspaceId()); user.setAccessToken(refreshToken); return Optional.of(user); @@ -195,7 +210,7 @@ public class UserServiceImpl implements IUserService { */ private UserDTO entityConvertToDTO(UserEntity entity) { if (entity == null) { - return new UserDTO(); + return null; } return UserDTO.builder() .userId(entity.getUserId()) diff --git a/src/main/java/com/dji/sample/media/model/CredentialsDTO.java b/src/main/java/com/dji/sample/media/model/CredentialsDTO.java index 4280a92..d00e953 100644 --- a/src/main/java/com/dji/sample/media/model/CredentialsDTO.java +++ b/src/main/java/com/dji/sample/media/model/CredentialsDTO.java @@ -17,6 +17,8 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class CredentialsDTO { + private static final int DELAY = 300; + private String accessKeyId; private String accessKeySecret; @@ -29,20 +31,20 @@ public class CredentialsDTO { this.accessKeyId = credentials.accessKey(); this.accessKeySecret = credentials.secretKey(); this.securityToken = credentials.sessionToken(); - this.expire = expire; + this.expire = expire - DELAY; } public CredentialsDTO(AssumeRoleResponse.Credentials credentials, long expire) { this.accessKeyId = credentials.getAccessKeyId(); this.accessKeySecret = credentials.getAccessKeySecret(); this.securityToken = credentials.getSecurityToken(); - this.expire = expire; + this.expire = expire - DELAY; } public CredentialsDTO(com.amazonaws.services.securitytoken.model.Credentials credentials) { this.accessKeyId = credentials.getAccessKeyId(); this.accessKeySecret = credentials.getSecretAccessKey(); this.securityToken = credentials.getSessionToken(); - this.expire = (credentials.getExpiration().getTime() - System.currentTimeMillis()) / 1000; + this.expire = (credentials.getExpiration().getTime() - System.currentTimeMillis()) / 1000 - DELAY; } } diff --git a/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java index 821594d..3a2526d 100644 --- a/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java +++ b/src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java @@ -7,6 +7,7 @@ import com.dji.sample.common.model.PaginationData; import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.service.impl.OssServiceContext; import com.dji.sample.manage.model.dto.DeviceDictionaryDTO; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.manage.service.IDeviceDictionaryService; import com.dji.sample.media.dao.IFileMapper; import com.dji.sample.media.model.FileUploadDTO; @@ -89,7 +90,8 @@ public class FileServiceImpl implements IFileService { Page pageData = mapper.selectPage( new Page(page, pageSize), new LambdaQueryWrapper() - .eq(MediaFileEntity::getWorkspaceId, workspaceId)); + .eq(MediaFileEntity::getWorkspaceId, workspaceId) + .orderByDesc(MediaFileEntity::getId)); List records = pageData.getRecords() .stream() .map(this::entityConvertToDto) @@ -133,7 +135,7 @@ public class FileServiceImpl implements IFileService { .mapToInt(Integer::intValue) .toArray(); Optional payloadDict = deviceDictionaryService - .getOneDictionaryInfoByTypeSubType(payloadModel[1], payloadModel[2]); + .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), payloadModel[1], payloadModel[2]); payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName())); } return builder.build(); diff --git a/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java index 0e92b0b..0e0063a 100644 --- a/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java @@ -3,6 +3,8 @@ package com.dji.sample.media.service.impl; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.component.mqtt.model.*; import com.dji.sample.component.mqtt.service.IMessageSenderService; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.service.IDeviceService; import com.dji.sample.media.model.FileUploadCallback; import com.dji.sample.media.model.FileUploadDTO; import com.dji.sample.media.model.MediaFileDTO; @@ -39,6 +41,9 @@ public class MediaServiceImpl implements IMediaService { @Autowired private IMessageSenderService messageSenderService; + @Autowired + private IDeviceService deviceService; + @Override public Boolean fastUpload(String workspaceId, String fingerprint) { return fileService.checkExist(workspaceId, fingerprint); @@ -77,7 +82,7 @@ public class MediaServiceImpl implements IMediaService { CommonTopicResponse data = CommonTopicResponse.builder() .timestamp(System.currentTimeMillis()) .method(EventsMethodEnum.FILE_UPLOAD_CALLBACK.getMethod()) - .data(ResponseResult.success()) + .data(RequestsReply.success()) .tid(receiver.getTid()) .bid(receiver.getBid()) .build(); @@ -85,6 +90,14 @@ public class MediaServiceImpl implements IMediaService { String jobId = callback.getFile().getExt().getFlightId(); Optional jobOpt = waylineJobService.getJobByJobId(jobId); if (jobOpt.isPresent()) { + // Set the drone sn that shoots the media + Optional dockDTO = deviceService.getDeviceBySn(jobOpt.get().getDockSn()); + dockDTO.ifPresent(dock -> callback.getFile().getExt().setSn(dock.getChildDeviceSn())); + + // set path + String objectKey = callback.getFile().getObjectKey(); + callback.getFile().setPath(objectKey.substring(objectKey.indexOf("/") + 1, objectKey.lastIndexOf("/"))); + int id = fileService.saveFile(jobOpt.get().getWorkspaceId(), callback.getFile()); if (id <= 0) { data.setData(ResponseResult.error()); diff --git a/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java b/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java index 93390e4..c4934ca 100644 --- a/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java +++ b/src/main/java/com/dji/sample/wayline/controller/WaylineFileController.java @@ -9,6 +9,7 @@ import com.dji.sample.wayline.model.param.WaylineQueryParam; import com.dji.sample.wayline.service.IWaylineFileService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -16,6 +17,7 @@ import java.io.IOException; import java.net.URL; import java.sql.SQLException; import java.util.List; +import java.util.Objects; import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; @@ -158,4 +160,21 @@ public class WaylineFileController { boolean isDel = waylineFileService.deleteByWaylineId(workspaceId, waylineId); return isDel ? ResponseResult.success() : ResponseResult.error("Failed to delete wayline."); } + + /** + * Import kmz wayline files. + * @param file + * @return + */ + @PostMapping("/{workspace_id}/waylines/file/upload") + public ResponseResult importKmzFile(HttpServletRequest request, MultipartFile file) { + if (Objects.isNull(file)) { + return ResponseResult.error("No file received."); + } + CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); + String workspaceId = customClaim.getWorkspaceId(); + String creator = customClaim.getUsername(); + waylineFileService.importKmzFile(file, workspaceId, creator); + return ResponseResult.success(); + } } diff --git a/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java b/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java index f066f54..e22de66 100644 --- a/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java +++ b/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java @@ -10,7 +10,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; import java.sql.SQLException; +import java.util.List; import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; @@ -35,12 +37,12 @@ public class WaylineJobController { * @throws SQLException */ @PostMapping("/{workspace_id}/flight-tasks") - public ResponseResult createJob(HttpServletRequest request, @RequestBody CreateJobParam param, + public ResponseResult createJob(HttpServletRequest request, @Valid @RequestBody CreateJobParam param, @PathVariable(name = "workspace_id") String workspaceId) throws SQLException { CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); customClaim.setWorkspaceId(workspaceId); - boolean isCreate = waylineJobService.createJob(param, customClaim); - return isCreate ? ResponseResult.success() : ResponseResult.error(); + + return waylineJobService.publishFlightTask(param, customClaim); } /** @@ -59,16 +61,16 @@ public class WaylineJobController { } /** - * Issue wayline mission to the dock for execution. - * @param jobId + * Send the command to cancel the jobs. + * @param jobIds * @param workspaceId * @return * @throws SQLException */ - @PostMapping("/{workspace_id}/jobs/{job_id}") - public ResponseResult publishJob(@PathVariable(name = "job_id") String jobId, + @DeleteMapping("/{workspace_id}/jobs") + public ResponseResult publishCancelJob(@RequestParam(name = "job_id") List jobIds, @PathVariable(name = "workspace_id") String workspaceId) throws SQLException { - waylineJobService.publishFlightTask(workspaceId, jobId); + waylineJobService.cancelFlightTask(workspaceId, jobIds); return ResponseResult.success(); } } diff --git a/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java index 736a8c5..c4b281d 100644 --- a/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java +++ b/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskCreateDTO.java @@ -18,7 +18,15 @@ public class FlightTaskCreateDTO { private String flightId; - private String type; + private Integer taskType; + + private Integer waylineType; + + private Long executeTime; private FlightTaskFileDTO file; + + private Integer rthAltitude; + + private Integer outOfControlAction; } diff --git a/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java index ca0accb..e5858ee 100644 --- a/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java +++ b/src/main/java/com/dji/sample/wayline/model/dto/FlightTaskFileDTO.java @@ -18,5 +18,5 @@ public class FlightTaskFileDTO { private String url; - private String sign; + private String fingerprint; } diff --git a/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java b/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java new file mode 100644 index 0000000..64e29a2 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java @@ -0,0 +1,39 @@ +package com.dji.sample.wayline.model.dto; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/27 + */ +public class KmzFileProperties { + + private KmzFileProperties() { + + } + + public static final String WAYLINE_FILE_SUFFIX = ".kmz"; + + public static final String FILE_DIR_FIRST = "wpmz"; + + public static final String FILE_DIR_SECOND_RES = "res"; + + public static final String FILE_DIR_SECOND_TEMPLATE = "template.kml"; + + public static final String FILE_DIR_SECOND_WAYLINES = "waylines.wpml"; + + public static final String TAG_WPML_PREFIX = "wpml:"; + + public static final String TAG_DRONE_INFO = "droneInfo"; + + public static final String TAG_DRONE_ENUM_VALUE = "droneEnumValue"; + + public static final String TAG_DRONE_SUB_ENUM_VALUE = "droneSubEnumValue"; + + public static final String TAG_PAYLOAD_INFO = "payloadInfo"; + + public static final String TAG_PAYLOAD_ENUM_VALUE = "payloadEnumValue"; + + public static final String TAG_PAYLOAD_SUB_ENUM_VALUE = "payloadSubEnumValue"; + + public static final String TAG_TEMPLATE_ID = "templateId"; +} diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java index a8820a7..9879879 100644 --- a/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java +++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineJobDTO.java @@ -32,12 +32,25 @@ public class WaylineJobDTO { private String workspaceId; - private String bid; + private Integer waylineType; - private String type; + private Integer taskType; + + private LocalDateTime executeTime; + + private LocalDateTime endTime; + + private Integer status; + + private Integer progress; private String username; - private LocalDateTime updateTime; + private Integer code; + + private Integer rthAltitude; + + private Integer outOfControlAction; + private Integer mediaCount; } diff --git a/src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java b/src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java index f2106f6..64800a0 100644 --- a/src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java +++ b/src/main/java/com/dji/sample/wayline/model/entity/WaylineJobEntity.java @@ -38,15 +38,36 @@ public class WaylineJobEntity implements Serializable { @TableField("workspace_id") private String workspaceId; - @TableField("bid") - private String bid; + @TableField("task_type") + private Integer taskType; - @TableField("type") - private String type; + @TableField("wayline_type") + private Integer waylineType; @TableField("username") private String username; + @TableField("execute_time") + private Long executeTime; + + @TableField("end_time") + private Long endTime; + + @TableField("error_code") + private Integer errorCode; + + @TableField("status") + private Integer status; + + @TableField("rth_altitude") + private Integer rthAltitude; + + @TableField("out_of_control") + private Integer outOfControlAction; + + @TableField("media_count") + private Integer mediaCount; + @TableField(value = "create_time", fill = FieldFill.INSERT) private Long createTime; diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineJobStatusEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineJobStatusEnum.java new file mode 100644 index 0000000..b33150b --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineJobStatusEnum.java @@ -0,0 +1,39 @@ +package com.dji.sample.wayline.model.enums; + +import lombok.Getter; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.3 + * @date 2022/9/26 + */ +@Getter +public enum WaylineJobStatusEnum { + + PENDING(1, false), + + IN_PROGRESS(2, false), + + SUCCESS(3, true), + + CANCEL(4, true), + + FAILED(5, true), + + UNKNOWN(6, true); + + int val; + + Boolean end; + + WaylineJobStatusEnum(int val, boolean end) { + this.end = end; + this.val = val; + } + + public static WaylineJobStatusEnum find(int val) { + return Arrays.stream(WaylineJobStatusEnum.values()).filter(statue -> statue.val == val).findAny().orElse(UNKNOWN); + } +} diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java new file mode 100644 index 0000000..4951d2c --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java @@ -0,0 +1,24 @@ +package com.dji.sample.wayline.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/11/14 + */ +@Getter +public enum WaylineMethodEnum { + + FLIGHT_TASK_PREPARE("flighttask_prepare"), + + FLIGHT_TASK_EXECUTE("flighttask_execute"), + + FLIGHT_TASK_CANCEL("flighttask_undo"); + + private String method; + + WaylineMethodEnum(String method) { + this.method = method; + } +} diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java new file mode 100644 index 0000000..89e556a --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java @@ -0,0 +1,22 @@ +package com.dji.sample.wayline.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/9/26 + */ +@Getter +public enum WaylineTaskTypeEnum { + + IMMEDIATE(0), + + TIMED(1); + + int val; + + WaylineTaskTypeEnum(int val) { + this.val = val; + } +} diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java new file mode 100644 index 0000000..2388e32 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.wayline.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2022/9/26 + */ +@Getter +public enum WaylineTemplateTypeEnum { + + WAYPOINT(0), + + MAPPING_2D(1), + + MAPPING_3D(2), + + MAPPING_STRIP(4); + + int val; + + WaylineTemplateTypeEnum(int val) { + this.val = val; + } +} diff --git a/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java b/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java index a186838..f8fe651 100644 --- a/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java +++ b/src/main/java/com/dji/sample/wayline/model/param/CreateJobParam.java @@ -1,6 +1,10 @@ package com.dji.sample.wayline.model.param; import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; /** * @author sean @@ -10,13 +14,30 @@ import lombok.Data; @Data public class CreateJobParam { + @NotBlank private String name; + @NotBlank private String fileId; + @NotBlank private String dockSn; - private String type; + @Range(max = 0) + @NotNull + private Integer waylineType; + + @Range(max = 1) + @NotNull + private Integer taskType; + + private Long executeTime; + + @Range(min = 20, max = 500) + @NotNull + private Integer rthAltitude; - private boolean immediate; + @NotNull + @Range(max = 2) + private Integer outOfControlAction; } diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java index 83f1820..4a722c8 100644 --- a/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java +++ b/src/main/java/com/dji/sample/wayline/service/IWaylineFileService.java @@ -3,6 +3,7 @@ package com.dji.sample.wayline.service; import com.dji.sample.common.model.PaginationData; import com.dji.sample.wayline.model.dto.WaylineFileDTO; import com.dji.sample.wayline.model.param.WaylineQueryParam; +import org.springframework.web.multipart.MultipartFile; import java.net.URL; import java.sql.SQLException; @@ -71,4 +72,13 @@ public interface IWaylineFileService { * @param waylineId */ Boolean deleteByWaylineId(String workspaceId, String waylineId); + + /** + * Import kmz wayline file. + * @param file + * @param workspaceId + * @param creator + * @return + */ + void importKmzFile(MultipartFile file, String workspaceId, String creator); } diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java index 4dc78d9..d20c53d 100644 --- a/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java +++ b/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java @@ -2,10 +2,14 @@ package com.dji.sample.wayline.service; import com.dji.sample.common.model.CustomClaim; import com.dji.sample.common.model.PaginationData; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; import com.dji.sample.wayline.model.dto.WaylineJobDTO; import com.dji.sample.wayline.model.param.CreateJobParam; +import org.springframework.messaging.MessageHeaders; import java.sql.SQLException; +import java.util.Collection; import java.util.Optional; /** @@ -16,20 +20,36 @@ import java.util.Optional; public interface IWaylineJobService { /** - * Create a wayline mission for the dock. + * Create wayline job in the database. * @param param - * @param customClaim user info + * @param customClaim user info * @return */ - Boolean createJob(CreateJobParam param, CustomClaim customClaim) throws SQLException; + Optional createWaylineJob(CreateJobParam param, CustomClaim customClaim); /** - * Issue wayline mission to the dock for execution. - * @param workspaceId + * Issue wayline mission to the dock. + * @param param + * @param customClaim user info + * @return + */ + ResponseResult publishFlightTask(CreateJobParam param, CustomClaim customClaim) throws SQLException; + + /** + * Execute the task immediately. * @param jobId + * @throws SQLException * @return */ - void publishFlightTask(String workspaceId, String jobId) throws SQLException; + Boolean executeFlightTask(String jobId); + + /** + * Cancel the task Base on job Ids. + * @param workspaceId + * @param jobIds + * @throws SQLException + */ + void cancelFlightTask(String workspaceId, Collection jobIds); /** * Query job information based on job id. @@ -53,4 +73,11 @@ public interface IWaylineJobService { * @return */ PaginationData getJobsByWorkspaceId(String workspaceId, long page, long pageSize); + + /** + * Process to get interface data of flight mission resources. + * @param receiver + * @param headers + */ + void flightTaskResourceGet(CommonTopicReceiver receiver, MessageHeaders headers); } diff --git a/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java index 46774bb..83b3247 100644 --- a/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java +++ b/src/main/java/com/dji/sample/wayline/service/impl/FlightTaskServiceImpl.java @@ -12,16 +12,25 @@ import com.dji.sample.component.websocket.service.IWebSocketManageService; import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.enums.UserTypeEnum; import com.dji.sample.wayline.model.dto.FlightTaskProgressReceiver; +import com.dji.sample.wayline.model.dto.WaylineJobDTO; +import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum; import com.dji.sample.wayline.service.IFlightTaskService; +import com.dji.sample.wayline.service.IWaylineJobService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.mqtt.support.MqttHeaders; import org.springframework.messaging.MessageHeaders; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + /** * @author sean * @version 1.1 @@ -46,18 +55,43 @@ public class FlightTaskServiceImpl implements IFlightTaskService { @Autowired private RedisOpsUtils redisOps; + @Autowired + private IWaylineJobService waylineJobService; + @Override @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND) public void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) { EventsReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference>(){}); eventsReceiver.setBid(receiver.getBid()); + eventsReceiver.setSn(receiver.getGateway()); + + FlightTaskProgressReceiver output = eventsReceiver.getOutput(); - log.info("Task progress: " + eventsReceiver.getOutput().getProgress().toString()); + log.info("Task progress: {}", output.getProgress().toString()); if (eventsReceiver.getResult() != ResponseResult.CODE_SUCCESS) { - log.error("Error code: " + eventsReceiver.getResult()); + log.error("Task progress ===> Error code: " + eventsReceiver.getResult()); + } + + EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus()); + if (statusEnum.getEnd()) { + WaylineJobDTO job = WaylineJobDTO.builder() + .jobId(receiver.getBid()) + .status(WaylineJobStatusEnum.SUCCESS.getVal()) + .endTime(LocalDateTime.now()) + .mediaCount(output.getExt().getMediaCount()) + .build(); + + if (EventsResultStatusEnum.OK != statusEnum) { + job.setCode(eventsReceiver.getResult()); + job.setStatus(WaylineJobStatusEnum.FAILED.getVal()); + } + + waylineJobService.updateJob(job); + redisOps.del(receiver.getBid()); } + redisOps.setWithExpire(receiver.getBid(), eventsReceiver, RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); DeviceDTO device = (DeviceDTO) redisOps.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); websocketMessageService.sendBatch( @@ -77,8 +111,47 @@ public class FlightTaskServiceImpl implements IFlightTaskService { .bid(receiver.getBid()) .method(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod()) .timestamp(System.currentTimeMillis()) - .data(ResponseResult.success()) + .data(RequestsReply.success()) .build()); } } + + @Scheduled(initialDelay = 10, fixedRate = 5, timeUnit = TimeUnit.SECONDS) + private void checkScheduledJob() { + Object jobIdValue = redisOps.zGetMin(RedisConst.WAYLINE_JOB); + log.info("Check the timed jobs of the wayline. {}", jobIdValue); + if (Objects.isNull(jobIdValue)) { + return; + } + String jobId = String.valueOf(jobIdValue); + double time = redisOps.zScore(RedisConst.WAYLINE_JOB, jobIdValue); + long now = System.currentTimeMillis(); + int offset = 30_000; + + // Expired tasks are deleted directly. + if (time < now - offset) { + redisOps.zRemove(RedisConst.WAYLINE_JOB, jobId); + waylineJobService.updateJob(WaylineJobDTO.builder() + .jobId(jobId) + .status(WaylineJobStatusEnum.FAILED.getVal()) + .endTime(LocalDateTime.now()) + .code(HttpStatus.SC_REQUEST_TIMEOUT).build()); + return; + } + + if (now <= time && time <= now + offset) { + try { + waylineJobService.executeFlightTask(jobId); + } catch (Exception e) { + log.info("The scheduled task delivery failed."); + waylineJobService.updateJob(WaylineJobDTO.builder() + .jobId(jobId) + .status(WaylineJobStatusEnum.FAILED.getVal()) + .endTime(LocalDateTime.now()) + .code(HttpStatus.SC_INTERNAL_SERVER_ERROR).build()); + } finally { + redisOps.zRemove(RedisConst.WAYLINE_JOB, jobId); + } + } + } } diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java index f12979a..7502950 100644 --- a/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java +++ b/src/main/java/com/dji/sample/wayline/service/impl/WaylineFileServiceImpl.java @@ -7,24 +7,36 @@ import com.dji.sample.common.model.Pagination; import com.dji.sample.common.model.PaginationData; import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.service.impl.OssServiceContext; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.wayline.dao.IWaylineFileMapper; +import com.dji.sample.wayline.model.dto.KmzFileProperties; import com.dji.sample.wayline.model.dto.WaylineFileDTO; import com.dji.sample.wayline.model.entity.WaylineFileEntity; import com.dji.sample.wayline.model.param.WaylineQueryParam; import com.dji.sample.wayline.service.IWaylineFileService; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Node; +import org.dom4j.io.SAXReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.DigestUtils; import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static com.dji.sample.wayline.model.dto.KmzFileProperties.WAYLINE_FILE_SUFFIX; /** * @author sean @@ -94,13 +106,17 @@ public class WaylineFileServiceImpl implements IWaylineFileService { file.setWaylineId(UUID.randomUUID().toString()); file.setWorkspaceId(workspaceId); - byte[] object = ossService.getObject(configuration.getBucket(), metadata.getObjectKey()); - if (object.length == 0) { - throw new RuntimeException("The file " + metadata.getObjectKey() + - " does not exist in the bucket[" + configuration.getBucket() + "]."); + if (!StringUtils.hasText(file.getSign())) { + try (InputStream object = ossService.getObject(configuration.getBucket(), metadata.getObjectKey())) { + if (object.available() == 0) { + throw new RuntimeException("The file " + metadata.getObjectKey() + + " does not exist in the bucket[" + configuration.getBucket() + "]."); + } + file.setSign(DigestUtils.md5DigestAsHex(object)); + } catch (IOException e) { + e.printStackTrace(); + } } - - file.setSign(DigestUtils.md5DigestAsHex(object)); int insertId = mapper.insert(file); return insertId > 0 ? file.getId() : insertId; } @@ -146,6 +162,78 @@ public class WaylineFileServiceImpl implements IWaylineFileService { return ossService.deleteObject(configuration.getBucket(), wayline.getObjectKey()); } + @Override + public void importKmzFile(MultipartFile file, String workspaceId, String creator) { + Optional waylineFileOpt = validKmzFile(file); + if (waylineFileOpt.isEmpty()) { + throw new RuntimeException("The file format is incorrect."); + } + + try { + WaylineFileDTO waylineFile = waylineFileOpt.get(); + waylineFile.setWaylineId(workspaceId); + waylineFile.setUsername(creator); + + ossService.putObject(configuration.getBucket(), waylineFile.getObjectKey(), file.getInputStream()); + this.saveWaylineFile(workspaceId, waylineFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private Optional validKmzFile(MultipartFile file) { + String filename = file.getOriginalFilename(); + if (Objects.nonNull(filename) && !filename.endsWith(WAYLINE_FILE_SUFFIX)) { + throw new RuntimeException("The file format is incorrect."); + } + try (ZipInputStream unzipFile = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) { + + ZipEntry nextEntry = unzipFile.getNextEntry(); + while (Objects.nonNull(nextEntry)) { + boolean isWaylines = (KmzFileProperties.FILE_DIR_FIRST + File.separator + KmzFileProperties.FILE_DIR_SECOND_WAYLINES).equals(nextEntry.getName()); + if (!isWaylines) { + nextEntry = unzipFile.getNextEntry(); + continue; + } + SAXReader reader = new SAXReader(); + Document document = reader.read(unzipFile); + if (!StandardCharsets.UTF_8.name().equals(document.getXMLEncoding())) { + throw new RuntimeException("The file encoding format is incorrect."); + } + + Node droneNode = document.selectSingleNode("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_INFO); + Node payloadNode = document.selectSingleNode("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_INFO); + if (Objects.isNull(droneNode) || Objects.isNull(payloadNode)) { + throw new RuntimeException("The file format is incorrect."); + } + + String type = droneNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_ENUM_VALUE); + String subType = droneNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_SUB_ENUM_VALUE); + String payloadType = payloadNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_ENUM_VALUE); + String payloadSubType = payloadNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_SUB_ENUM_VALUE); + String templateId = document.valueOf("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_TEMPLATE_ID); + + if (!StringUtils.hasText(type) || !StringUtils.hasText(subType) || + !StringUtils.hasText(payloadSubType) || !StringUtils.hasText(payloadType) || + !StringUtils.hasText(templateId)) { + throw new RuntimeException("The file format is incorrect."); + } + + return Optional.of(WaylineFileDTO.builder() + .droneModelKey(String.format("%s-%s-%s", DeviceDomainEnum.SUB_DEVICE.getVal(), type, subType)) + .payloadModelKeys(List.of(String.format("%s-%s-%s",DeviceDomainEnum.PAYLOAD.getVal(), payloadType, payloadSubType))) + .objectKey(configuration.getObjectDirPrefix() + File.separator + filename) + .name(filename.substring(0, filename.lastIndexOf(WAYLINE_FILE_SUFFIX))) + .sign(DigestUtils.md5DigestAsHex(file.getInputStream())) + .templateTypes(List.of(Integer.parseInt(templateId))) + .build()); + } + + } catch (IOException | DocumentException e) { + e.printStackTrace(); + } + return Optional.empty(); + } /** * Convert database entity objects into wayline data transfer object. * @param entity @@ -192,6 +280,7 @@ public class WaylineFileServiceImpl implements IWaylineFileService { .map(String::valueOf) .collect(Collectors.joining(","))) .favorited(file.getFavorited()) + .sign(file.getSign()) .build(); } diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java index 9a2336c..5313863 100644 --- a/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java +++ b/src/main/java/com/dji/sample/wayline/service/impl/WaylineJobServiceImpl.java @@ -3,40 +3,44 @@ package com.dji.sample.wayline.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.dji.sample.common.error.CommonErrorEnum; import com.dji.sample.common.model.CustomClaim; import com.dji.sample.common.model.Pagination; import com.dji.sample.common.model.PaginationData; -import com.dji.sample.component.mqtt.model.CommonTopicResponse; -import com.dji.sample.component.mqtt.model.ServiceReply; -import com.dji.sample.component.mqtt.model.ServicesMethodEnum; -import com.dji.sample.component.mqtt.model.TopicConst; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.mqtt.model.*; import com.dji.sample.component.mqtt.service.IMessageSenderService; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.service.IDeviceService; import com.dji.sample.wayline.dao.IWaylineJobMapper; -import com.dji.sample.wayline.model.dto.FlightTaskCreateDTO; -import com.dji.sample.wayline.model.dto.FlightTaskFileDTO; -import com.dji.sample.wayline.model.dto.WaylineFileDTO; -import com.dji.sample.wayline.model.dto.WaylineJobDTO; +import com.dji.sample.wayline.model.dto.*; import com.dji.sample.wayline.model.entity.WaylineJobEntity; +import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum; +import com.dji.sample.wayline.model.enums.WaylineMethodEnum; +import com.dji.sample.wayline.model.enums.WaylineTaskTypeEnum; import com.dji.sample.wayline.model.param.CreateJobParam; import com.dji.sample.wayline.service.IWaylineFileService; import com.dji.sample.wayline.service.IWaylineJobService; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; import java.net.URL; import java.sql.SQLException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -64,10 +68,18 @@ public class WaylineJobServiceImpl implements IWaylineJobService { @Autowired private RedisOpsUtils redisOps; + @Autowired + private ObjectMapper objectMapper; + + @Override - public Boolean createJob(CreateJobParam param, CustomClaim customClaim) throws SQLException { - if (param == null) { - return false; + public Optional createWaylineJob(CreateJobParam param, CustomClaim customClaim) { + if (Objects.isNull(param)) { + return Optional.empty(); + } + // Immediate tasks, allocating time on the backend. + if (Objects.isNull(param.getExecuteTime())) { + param.setExecuteTime(System.currentTimeMillis()); } WaylineJobEntity jobEntity = WaylineJobEntity.builder() .name(param.getName()) @@ -76,75 +88,203 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .username(customClaim.getUsername()) .workspaceId(customClaim.getWorkspaceId()) .jobId(UUID.randomUUID().toString()) - .type(param.getType()) + .executeTime(param.getExecuteTime()) + .status(WaylineJobStatusEnum.PENDING.getVal()) + .taskType(param.getTaskType()) + .waylineType(param.getWaylineType()) + .outOfControlAction(param.getOutOfControlAction()) + .rthAltitude(param.getRthAltitude()) .build(); int id = mapper.insert(jobEntity); if (id <= 0) { - return false; + return Optional.empty(); } - if (param.isImmediate()) { - publishFlightTask(jobEntity.getWorkspaceId(), jobEntity.getJobId()); - } - return true; + return Optional.ofNullable(this.entity2Dto(jobEntity)); } @Override - public void publishFlightTask(String workspaceId, String jobId) throws SQLException { - // get job - Optional waylineJob = this.getJobByJobId(jobId); - if (waylineJob.isEmpty()) { - throw new IllegalArgumentException("Job doesn't exist."); + public ResponseResult publishFlightTask(CreateJobParam param, CustomClaim customClaim) throws SQLException { + Optional waylineJobOpt = this.createWaylineJob(param, customClaim); + if (waylineJobOpt.isEmpty()) { + throw new SQLException("Failed to create wayline job."); } + WaylineJobDTO waylineJob = waylineJobOpt.get(); - long expire = redisOps.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + waylineJob.get().getDockSn()); - if (expire < 0) { + boolean isOnline = deviceService.checkDeviceOnline(waylineJob.getDockSn()); + if (!isOnline) { throw new RuntimeException("Dock is offline."); } // get wayline file - Optional waylineFile = waylineFileService.getWaylineByWaylineId(workspaceId, waylineJob.get().getFileId()); + Optional waylineFile = waylineFileService.getWaylineByWaylineId(waylineJob.getWorkspaceId(), waylineJob.getFileId()); if (waylineFile.isEmpty()) { - throw new IllegalArgumentException("Wayline file doesn't exist."); + throw new SQLException("Wayline file doesn't exist."); } // get file url - URL url = waylineFileService.getObjectUrl(workspaceId, waylineFile.get().getWaylineId()); + URL url = waylineFileService.getObjectUrl(waylineJob.getWorkspaceId(), waylineFile.get().getWaylineId()); - WaylineJobDTO job = waylineJob.get(); FlightTaskCreateDTO flightTask = FlightTaskCreateDTO.builder() - .flightId(jobId) - .type(job.getType()) + .flightId(waylineJob.getJobId()) + .executeTime(waylineJob.getExecuteTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()) + .taskType(waylineJob.getTaskType()) + .waylineType(waylineJob.getWaylineType()) + .rthAltitude(waylineJob.getRthAltitude()) + .outOfControlAction(waylineJob.getOutOfControlAction()) .file(FlightTaskFileDTO.builder() .url(url.toString()) - .sign(waylineFile.get().getSign()) + .fingerprint(waylineFile.get().getSign()) .build()) .build(); + String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + + waylineJob.getDockSn() + TopicConst.SERVICES_SUF; + CommonTopicResponse response = CommonTopicResponse.builder() + .tid(UUID.randomUUID().toString()) + .bid(waylineJob.getJobId()) + .timestamp(System.currentTimeMillis()) + .data(flightTask) + .method(WaylineMethodEnum.FLIGHT_TASK_PREPARE.getMethod()) + .build(); + + ServiceReply serviceReply = messageSender.publishWithReply(topic, response); + if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { + log.info("Prepare task ====> Error code: {}", serviceReply.getResult()); + this.updateJob(WaylineJobDTO.builder() + .workspaceId(waylineJob.getWorkspaceId()) + .jobId(waylineJob.getJobId()) + .status(WaylineJobStatusEnum.FAILED.getVal()) + .endTime(LocalDateTime.now()) + .code(serviceReply.getResult()).build()); + return ResponseResult.error("Prepare task ====> Error code: " + serviceReply.getResult()); + } + + // Issue an immediate task execution command. + if (WaylineTaskTypeEnum.IMMEDIATE.getVal() == waylineJob.getTaskType()) { + if (!executeFlightTask(waylineJob.getJobId())) { + return ResponseResult.error("Failed to execute job."); + } + } + + if (WaylineTaskTypeEnum.TIMED.getVal() == waylineJob.getTaskType()) { + boolean isAdd = redisOps.zAdd(RedisConst.WAYLINE_JOB, waylineJob.getJobId(), + waylineJob.getExecuteTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + if (!isAdd) { + return ResponseResult.error("Failed to create scheduled job."); + } + } + + return ResponseResult.success(); + } + + @Override + public Boolean executeFlightTask(String jobId) { + // get job + Optional waylineJob = this.getJobByJobId(jobId); + if (waylineJob.isEmpty()) { + throw new IllegalArgumentException("Job doesn't exist."); + } + + boolean isOnline = deviceService.checkDeviceOnline(waylineJob.get().getDockSn()); + if (!isOnline) { + throw new RuntimeException("Dock is offline."); + } + + WaylineJobDTO job = waylineJob.get(); + FlightTaskCreateDTO flightTask = FlightTaskCreateDTO.builder().flightId(jobId).build(); + String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + job.getDockSn() + TopicConst.SERVICES_SUF; CommonTopicResponse response = CommonTopicResponse.builder() .tid(UUID.randomUUID().toString()) - .bid(UUID.randomUUID().toString()) + .bid(jobId) .timestamp(System.currentTimeMillis()) .data(flightTask) - .method(ServicesMethodEnum.FLIGHTTASK_CREATE.getMethod()) + .method(WaylineMethodEnum.FLIGHT_TASK_EXECUTE.getMethod()) .build(); - Optional serviceReplyOpt = messageSender.publishWithReply(topic, response); - if (serviceReplyOpt.isEmpty()) { - log.info("Timeout to receive reply."); - throw new RuntimeException("Timeout to receive reply."); + ServiceReply serviceReply = messageSender.publishWithReply(topic, response); + if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { + log.info("Execute job ====> Error code: {}", serviceReply.getResult()); + this.updateJob(WaylineJobDTO.builder() + .jobId(jobId) + .status(WaylineJobStatusEnum.FAILED.getVal()) + .endTime(LocalDateTime.now()) + .code(serviceReply.getResult()).build()); + return false; } - if (serviceReplyOpt.get().getResult() != 0) { - log.info("Error code: {}", serviceReplyOpt.get().getResult()); - throw new RuntimeException("Error code: " + serviceReplyOpt.get().getResult()); + + this.updateJob(WaylineJobDTO.builder() + .jobId(jobId) + .status(WaylineJobStatusEnum.IN_PROGRESS.getVal()) + .build()); + redisOps.setWithExpire(jobId, + EventsReceiver.builder().bid(jobId).sn(job.getDockSn()).build(), + RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); + return true; + } + + @Override + public void cancelFlightTask(String workspaceId, Collection jobIds) { + List waylineJobs = mapper.selectList( + new LambdaQueryWrapper() + .or(wrapper -> jobIds.forEach(id -> wrapper.eq(WaylineJobEntity::getJobId, id)))); + + // Check if the job have ended. + List endJobs = waylineJobs.stream() + .filter(job -> WaylineJobStatusEnum.find(job.getStatus()).getEnd()) + .map(WaylineJobEntity::getName) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(endJobs)) { + throw new IllegalArgumentException("There are jobs that have ended." + Arrays.toString(endJobs.toArray())); } - job.setBid(response.getBid()); - boolean isUpd = this.updateJob(job); - if (!isUpd) { - throw new SQLException("Failed to update data."); + Set ids = waylineJobs.stream().map(WaylineJobEntity::getJobId).collect(Collectors.toSet()); + for (String id : jobIds) { + if (!ids.contains(id)) { + throw new IllegalArgumentException("Job id " + id + " doesn't exist."); + } } + + // Group job id by dock sn. + Map> dockJobs = waylineJobs.stream() + .collect(Collectors.groupingBy(WaylineJobEntity::getDockSn, + Collectors.mapping(WaylineJobEntity::getJobId, Collectors.toList()))); + dockJobs.forEach((dockSn, idList) -> this.publishCancelTask(workspaceId, dockSn, idList)); + + } + + private void publishCancelTask(String workspaceId, String dockSn, List jobIds) { + boolean isOnline = deviceService.checkDeviceOnline(dockSn); + if (isOnline) { + throw new RuntimeException("Dock is offline."); + } + String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + dockSn + TopicConst.SERVICES_SUF; + + CommonTopicResponse response = CommonTopicResponse.builder() + .tid(UUID.randomUUID().toString()) + .bid(UUID.randomUUID().toString()) + .timestamp(System.currentTimeMillis()) + .data(Map.of(MapKeyConst.FLIGHT_IDS, jobIds)) + .method(WaylineMethodEnum.FLIGHT_TASK_CANCEL.getMethod()) + .build(); + + ServiceReply serviceReply = messageSender.publishWithReply(topic, response); + if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { + log.info("Cancel job ====> Error code: {}", serviceReply.getResult()); + throw new RuntimeException("Failed to cancel the wayline job of " + dockSn); + } + + for (String jobId : jobIds) { + this.updateJob(WaylineJobDTO.builder() + .workspaceId(workspaceId) + .jobId(jobId) + .status(WaylineJobStatusEnum.CANCEL.getVal()) + .endTime(LocalDateTime.now()) + .build()); + redisOps.zRemove(RedisConst.WAYLINE_JOB, jobId); + } + } @Override @@ -159,9 +299,7 @@ public class WaylineJobServiceImpl implements IWaylineJobService { public Boolean updateJob(WaylineJobDTO dto) { return mapper.update(this.dto2Entity(dto), new LambdaUpdateWrapper() - .eq(WaylineJobEntity::getWorkspaceId, dto.getWorkspaceId()) - .eq(WaylineJobEntity::getJobId, dto.getJobId())) - > 0; + .eq(WaylineJobEntity::getJobId, dto.getJobId())) > 0; } @Override @@ -169,7 +307,8 @@ public class WaylineJobServiceImpl implements IWaylineJobService { Page pageData = mapper.selectPage( new Page(page, pageSize), new LambdaQueryWrapper() - .eq(WaylineJobEntity::getWorkspaceId, workspaceId)); + .eq(WaylineJobEntity::getWorkspaceId, workspaceId) + .orderByDesc(WaylineJobEntity::getId)); List records = pageData.getRecords() .stream() .map(this::entity2Dto) @@ -178,14 +317,76 @@ public class WaylineJobServiceImpl implements IWaylineJobService { return new PaginationData(records, new Pagination(pageData)); } + + @Override + @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_FLIGHT_TASK_RESOURCE_GET, outputChannel = ChannelName.OUTBOUND) + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + public void flightTaskResourceGet(CommonTopicReceiver receiver, MessageHeaders headers) { + Map jobIdMap = objectMapper.convertValue(receiver.getData(), new TypeReference>() {}); + String jobId = jobIdMap.get(MapKeyConst.FLIGHT_ID); + + CommonTopicResponse.CommonTopicResponseBuilder builder = CommonTopicResponse.builder() + .tid(receiver.getTid()) + .bid(receiver.getBid()) + .method(RequestsMethodEnum.FLIGHT_TASK_RESOURCE_GET.getMethod()) + .timestamp(System.currentTimeMillis()); + + String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString() + TopicConst._REPLY_SUF; + + Optional waylineJobOpt = this.getJobByJobId(jobId); + if (waylineJobOpt.isEmpty()) { + builder.data(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT)); + messageSender.publish(topic, builder.build()); + return; + } + + WaylineJobDTO waylineJob = waylineJobOpt.get(); + + // get wayline file + Optional waylineFile = waylineFileService.getWaylineByWaylineId(waylineJob.getWorkspaceId(), waylineJob.getFileId()); + if (waylineFile.isEmpty()) { + builder.data(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT)); + messageSender.publish(topic, builder.build()); + return; + } + + // get file url + URL url = null; + try { + url = waylineFileService.getObjectUrl(waylineJob.getWorkspaceId(), waylineFile.get().getWaylineId()); + builder.data(RequestsReply.success(FlightTaskCreateDTO.builder() + .file(FlightTaskFileDTO.builder() + .url(url.toString()) + .fingerprint(waylineFile.get().getSign()) + .build()) + .build())); + + } catch (SQLException | NullPointerException e) { + e.printStackTrace(); + builder.data(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT)); + messageSender.publish(topic, builder.build()); + return; + } + + messageSender.publish(topic, builder.build()); + + } + private WaylineJobEntity dto2Entity(WaylineJobDTO dto) { WaylineJobEntity.WaylineJobEntityBuilder builder = WaylineJobEntity.builder(); if (dto == null) { return builder.build(); } - return builder.type(dto.getType()) - .bid(dto.getBid()) + if (Objects.nonNull(dto.getEndTime())) { + builder.endTime(dto.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + } + if (Objects.nonNull(dto.getExecuteTime())) { + builder.executeTime(dto.getExecuteTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + } + return builder.status(dto.getStatus()) + .mediaCount(dto.getMediaCount()) .name(dto.getJobName()) + .errorCode(dto.getCode()) .build(); } @@ -193,10 +394,9 @@ public class WaylineJobServiceImpl implements IWaylineJobService { if (entity == null) { return null; } - return WaylineJobDTO.builder() + + WaylineJobDTO.WaylineJobDTOBuilder builder = WaylineJobDTO.builder() .jobId(entity.getJobId()) - .bid(entity.getBid()) - .updateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault())) .jobName(entity.getName()) .fileId(entity.getFileId()) .fileName(waylineFileService.getWaylineByWaylineId(entity.getWorkspaceId(), entity.getFileId()) @@ -206,7 +406,24 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .orElse(DeviceDTO.builder().build()).getNickname()) .username(entity.getUsername()) .workspaceId(entity.getWorkspaceId()) - .type(entity.getType()) - .build(); + .status(entity.getStatus()) + .code(entity.getErrorCode()) + .executeTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getExecuteTime()), ZoneId.systemDefault())) + .taskType(entity.getTaskType()) + .waylineType(entity.getWaylineType()) + .rthAltitude(entity.getRthAltitude()) + .outOfControlAction(entity.getOutOfControlAction()) + .mediaCount(entity.getMediaCount()); + + if (Objects.nonNull(entity.getEndTime())) { + builder.endTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault())); + } + if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() && redisOps.getExpire(entity.getJobId()) > 0) { + EventsReceiver taskProgress = (EventsReceiver) redisOps.get(entity.getJobId()); + if (Objects.nonNull(taskProgress.getOutput()) && Objects.nonNull(taskProgress.getOutput().getProgress())) { + builder.progress(taskProgress.getOutput().getProgress().getPercent()); + } + } + return builder.build(); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 80c32ce..027299f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -107,3 +107,7 @@ logging: com.dji.sample: debug file: name: logs/cloud-api-sample.log + +ntp: + server: + host: Google.mzr.me \ No newline at end of file