diff --git a/api/Cloud API Demo.postman_collection.json b/api/Cloud API Demo.postman_collection.json index a1535ea..6bfd878 100644 --- a/api/Cloud API Demo.postman_collection.json +++ b/api/Cloud API Demo.postman_collection.json @@ -887,7 +887,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2NzAzMTU2MDEsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2NzA0MDIwMDEsImlhdCI6MTY3MDMxNTYwMSwidXNlcm5hbWUiOiJhZG1pblBDIn0.yh8SkHZVsoIXo_vtlTGNB-ZX92XayalGe_q7mNRVcdI", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2ODIyMzI5MDYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3Njg2MzI5MDYsImlhdCI6MTY4MjIzMjkwNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ilO-3PcvWAX9r8z3AR4VAw3kVhavYjiTx_187ACBc1M", "type": "string" }, { @@ -1537,7 +1537,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg2NTk4MjYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg3NDYyMjYsImlhdCI6MTY2ODY1OTgyNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ykCpfJcReeb3etUzmNMQk1n0vaoDT6dl47J_aHRoTbU", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2ODAyNjAxNTYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3NjY2NjAxNTYsImlhdCI6MTY4MDI2MDE1NiwidXNlcm5hbWUiOiJhZG1pblBDIn0._QhvfhBxxfQN7xpFqZma1rCYbBtouo2pErtm8737L_8", "type": "string" }, { @@ -1578,7 +1578,7 @@ "header": [], "body": { "mode": "raw", - "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}", + "raw": "{\r\n \"name\": \"\",\r\n \"file_id\": \"\",\r\n \"dock_sn\": \"\",\r\n \"wayline_type\": 0,\r\n \"task_type\": 0,\r\n \"task_days\": [1676029468],\r\n \"task_periods\": [[1676029468]],\r\n \"rth_altitude\": 20,\r\n \"out_of_control_action\": 1\r\n}", "options": { "raw": { "language": "json" @@ -1679,6 +1679,64 @@ } }, "response": [] + }, + { + "name": "Pause Job", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"status\": 0\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs/{{job_id}}", + "host": [ + "{{base_url}}{{wayline_version}}" + ], + "path": [ + "workspaces", + "{{workspace_id}}", + "jobs", + "{{job_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Resume Job", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"status\": 1\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{wayline_version}}/workspaces/{{workspace_id}}/jobs/{{job_id}}", + "host": [ + "{{base_url}}{{wayline_version}}" + ], + "path": [ + "workspaces", + "{{workspace_id}}", + "jobs", + "{{job_id}}" + ] + } + }, + "response": [] } ], "auth": { @@ -1686,7 +1744,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njg2NTk4MjYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njg3NDYyMjYsImlhdCI6MTY2ODY1OTgyNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ykCpfJcReeb3etUzmNMQk1n0vaoDT6dl47J_aHRoTbU", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Nzg4NjM0NzMsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3NjUyNjM0NzMsImlhdCI6MTY3ODg2MzQ3MywidXNlcm5hbWUiOiJhZG1pblBDIn0.r3ODgJtAHxrBCzDnCwTDCdUq8hLyfIUiDYzasYAIUII", "type": "string" }, { @@ -1727,7 +1785,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"action\": 0\r\n}", + "raw": "", "options": { "raw": { "language": "json" @@ -1735,7 +1793,7 @@ } }, "url": { - "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/alarm_state_switch", + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/return_home", "host": [ "{{base_url}}{{control_version}}" ], @@ -1743,7 +1801,250 @@ "devices", "{{device_sn}}", "jobs", - "alarm_state_switch" + "return_home" + ] + } + }, + "response": [] + }, + { + "name": "Web Drc Connect", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"client_id\": \"xxx\",\r\n \"expire_sec\": 1800\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/workspaces/{{workspace_id}}/drc/connect", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "workspaces", + "{{workspace_id}}", + "drc", + "connect" + ] + } + }, + "response": [] + }, + { + "name": "Enter Drc Mode", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"client_id\": \"\",\r\n \"dock_sn\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/workspaces/{{workspace_id}}/drc/enter", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "workspaces", + "{{workspace_id}}", + "drc", + "enter" + ] + } + }, + "response": [] + }, + { + "name": "Drc Mode Exit", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"client_id\": \"\",\r\n \"dock_sn\": \"\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/workspaces/{{workspace_id}}/drc/exit", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "workspaces", + "{{workspace_id}}", + "drc", + "exit" + ] + } + }, + "response": [] + }, + { + "name": "Fly to Point", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"max_speed\": 15,\r\n \"points\":[\r\n {\r\n \"latitude\": 22.5818,\r\n \"longitude\": 113.9394,\r\n \"height\": 20\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/fly-to-point", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "devices", + "{{device_sn}}", + "jobs", + "fly-to-point" + ] + } + }, + "response": [] + }, + { + "name": "Stop Flying to Point", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/fly-to-point", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "devices", + "{{device_sn}}", + "jobs", + "fly-to-point" + ] + } + }, + "response": [] + }, + { + "name": "Take off to Point", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"target_latitude\": 22.579,\r\n \"target_longitude\": 113.9392,\r\n \"target_height\": 20,\r\n \"security_takeoff_height\": 20,\r\n \"rth_altitude\": 20,\r\n \"rc_lost_action\": 0,\r\n \"exit_wayline_when_rc_lost\": 0,\r\n \"max_speed\": 12\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/jobs/takeoff-to-point", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "devices", + "{{device_sn}}", + "jobs", + "takeoff-to-point" + ] + } + }, + "response": [] + }, + { + "name": "Payload Commands", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"cmd\":\"camera_mode_switch\",\r\n \"data\":{\r\n \"payload_index\":\"53-0-0\",\r\n \"camera_mode\": 1\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/payload/commands", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "devices", + "{{device_sn}}", + "payload", + "commands" + ] + } + }, + "response": [] + }, + { + "name": "Flight Authority Grab", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/authority/flight", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "devices", + "{{device_sn}}", + "authority", + "flight" + ] + } + }, + "response": [] + }, + { + "name": "Payload Authority Grab", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"payload_index\":\"53-0-0\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}{{control_version}}/devices/{{device_sn}}/authority/payload", + "host": [ + "{{base_url}}{{control_version}}" + ], + "path": [ + "devices", + "{{device_sn}}", + "authority", + "payload" ] } }, @@ -1755,7 +2056,7 @@ "apikey": [ { "key": "value", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2Njk2MzMzMzQsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE2Njk3MTk3MzQsImlhdCI6MTY2OTYzMzMzNCwidXNlcm5hbWUiOiJhZG1pblBDIn0.OoIfdpyI5eL6bFm8akq8_stzClQU41YpIJkx6_kxVHU", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2VfaWQiOiJlM2RlYTBmNS0zN2YyLTRkNzktYWU1OC00OTBhZjMyMjgwNjkiLCJzdWIiOiJDbG91ZEFwaVNhbXBsZSIsInVzZXJfdHlwZSI6IjEiLCJuYmYiOjE2ODIyMzI5MDYsImxvZyI6IkxvZ2dlcltjb20uZGppLnNhbXBsZS5jb21tb24ubW9kZWwuQ3VzdG9tQ2xhaW1dIiwiaXNzIjoiREpJIiwiaWQiOiJhMTU1OWU3Yy04ZGQ4LTQ3ODAtYjk1Mi0xMDBjYzQ3OTdkYTIiLCJleHAiOjE3Njg2MzI5MDYsImlhdCI6MTY4MjIzMjkwNiwidXNlcm5hbWUiOiJhZG1pblBDIn0.ilO-3PcvWAX9r8z3AR4VAw3kVhavYjiTx_187ACBc1M", "type": "string" }, { diff --git a/pom.xml b/pom.xml index 66e3efa..4f0fea6 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.dji cloud-api-sample - 1.3.1 + 1.4.0 cloud-api-sample diff --git a/sql/cloud_sample.sql b/sql/cloud_sample.sql index 3ff45db..74fe68c 100644 --- a/sql/cloud_sample.sql +++ b/sql/cloud_sample.sql @@ -66,7 +66,7 @@ CREATE TABLE `manage_device` ( `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'dock, drone, remote control', `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'undefined' COMMENT 'model of the device. This parameter corresponds to the device name in the device dictionary table.', `user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'The account used when the device was bound.', - `nickname` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'custom name of the device', + `nickname` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'custom name of the device', `workspace_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'The workspace to which the current device belongs.', `device_type` int NOT NULL DEFAULT '-1' COMMENT 'This parameter corresponds to the device type in the device dictionary table.', `sub_type` int NOT NULL DEFAULT '-1' COMMENT 'This parameter corresponds to the sub type in the device dictionary table.', @@ -226,6 +226,7 @@ CREATE TABLE `manage_device_payload` ( `payload_index` smallint NOT NULL COMMENT 'The location of the payload on the device.', `device_sn` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'Which device the current payload belongs to.', `payload_desc` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `control_source` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `create_time` bigint NOT NULL, `update_time` bigint NOT NULL, PRIMARY KEY (`id`), @@ -452,7 +453,7 @@ CREATE TABLE `wayline_job` ( `completed_time` bigint DEFAULT NULL COMMENT 'actual end time', `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'The name of the creator.', `begin_time` bigint NOT NULL COMMENT 'planned begin time', - `end_time` bigint NOT NULL COMMENT 'planned end time', + `end_time` bigint DEFAULT NULL COMMENT 'planned end time', `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', diff --git a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java index 953fd2d..0b6b6b0 100644 --- a/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java +++ b/src/main/java/com/dji/sample/component/GlobalExceptionHandler.java @@ -36,7 +36,7 @@ public class GlobalExceptionHandler { @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class}) public ResponseResult methodArgumentNotValidExceptionHandler(BindException e) { e.printStackTrace(); - return ResponseResult.error(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + return ResponseResult.error(e.getFieldError().getField() + e.getFieldError().getDefaultMessage()); } } diff --git a/src/main/java/com/dji/sample/component/GlobalScheduleService.java b/src/main/java/com/dji/sample/component/GlobalScheduleService.java index e092ad4..7e77a34 100644 --- a/src/main/java/com/dji/sample/component/GlobalScheduleService.java +++ b/src/main/java/com/dji/sample/component/GlobalScheduleService.java @@ -7,6 +7,7 @@ import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.manage.service.IDeviceService; import com.dji.sample.wayline.service.IWaylineJobService; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; @@ -32,11 +33,12 @@ public class GlobalScheduleService { @Autowired private IWaylineJobService waylineJobService; - + @Autowired + private ObjectMapper mapper; /** * Check the status of the devices every 30 seconds. It is recommended to use cache. */ - @Scheduled(initialDelay = 30, fixedRate = 30, timeUnit = TimeUnit.SECONDS) + @Scheduled(initialDelay = 10, fixedRate = 30, timeUnit = TimeUnit.SECONDS) private void deviceStatusListen() { int start = RedisConst.DEVICE_ONLINE_PREFIX.length(); @@ -49,8 +51,9 @@ public class GlobalScheduleService { } else { deviceService.unsubscribeTopicOffline(key.substring(start)); deviceService.pushDeviceOfflineTopo(device.getWorkspaceId(), device.getDeviceSn()); - RedisOpsUtils.hashDel(RedisConst.LIVE_CAPACITY, new String[]{key}); - RedisOpsUtils.del(RedisConst.HMS_PREFIX + key); + RedisOpsUtils.hashDel(RedisConst.LIVE_CAPACITY, new String[]{device.getDeviceSn()}); + RedisOpsUtils.del(RedisConst.HMS_PREFIX + device.getDeviceSn()); + RedisOpsUtils.del(RedisConst.OSD_PREFIX + device.getDeviceSn()); } RedisOpsUtils.del(key); } diff --git a/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java index 69c6398..ba8d939 100644 --- a/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java +++ b/src/main/java/com/dji/sample/component/mqtt/config/MqttConfiguration.java @@ -5,6 +5,7 @@ import com.dji.sample.common.util.JwtUtil; import com.dji.sample.component.mqtt.model.MqttClientOptions; import com.dji.sample.component.mqtt.model.MqttProtocolEnum; import com.dji.sample.component.mqtt.model.MqttUseEnum; +import com.dji.sample.control.model.dto.MqttBrokerDTO; import lombok.Data; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -70,6 +71,32 @@ public class MqttConfiguration { return addr.toString(); } + /** + * Get the connection parameters of the mqtt client of the drc link. + * @param clientId + * @param username + * @param age The validity period of the token. unit: s + * @param map Custom data added in token. + * @return + */ + public static MqttBrokerDTO getMqttBrokerWithDrc(String clientId, String username, Long age, Map map) { + if (!mqtt.containsKey(MqttUseEnum.DRC)) { + throw new RuntimeException("Please configure the drc link parameters of mqtt in the backend configuration file first."); + } + Algorithm algorithm = JwtUtil.algorithm; + + String token = JwtUtil.createToken(map, age, algorithm, null, null); + + return MqttBrokerDTO.builder() + .address(getMqttAddress(mqtt.get(MqttUseEnum.DRC))) + .username(username) + .clientId(clientId) + .expireTime(System.currentTimeMillis() / 1000 + age) + .password(token) + .build(); + } + + @Bean public MqttConnectOptions mqttConnectOptions() { MqttClientOptions customizeOptions = getBasicClientOptions(); 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 8e168e8..8ff8629 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 @@ -107,6 +107,11 @@ public class MqttMessageChannel { return new DirectChannel(); } + @Bean(name = ChannelName.OUTBOUND_EVENTS) + public MessageChannel eventsOutboundChannel() { + return new DirectChannel(); + } + @Bean(name = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS) public MessageChannel eventsFlightTaskProgressChannel() { return new DirectChannel(); @@ -172,4 +177,28 @@ public class MqttMessageChannel { return new DirectChannel(); } + @Bean(name = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_READY) + public MessageChannel eventsEventsFlightTaskReady() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_EVENTS_FLY_TO_POINT_PROGRESS) + public MessageChannel eventsFlyToPointProgress() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS) + public MessageChannel eventsTakeoffToPointProgress() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_EVENTS_DRC_STATUS_NOTIFY) + public MessageChannel eventsDrcStatusNotify() { + return new DirectChannel(); + } + + @Bean(name = ChannelName.INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY) + public MessageChannel eventsDrcModeExitNotify() { + return new DirectChannel(); + } } diff --git a/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java b/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java index 49034b3..152ae81 100644 --- a/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java +++ b/src/main/java/com/dji/sample/component/mqtt/handler/EventsRouter.java @@ -1,17 +1,20 @@ package com.dji.sample.component.mqtt.handler; -import com.dji.sample.component.mqtt.model.ChannelName; -import com.dji.sample.component.mqtt.model.CommonTopicReceiver; -import com.dji.sample.component.mqtt.model.EventsMethodEnum; +import com.dji.sample.component.mqtt.model.*; +import com.dji.sample.component.mqtt.service.IMessageSenderService; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.mqtt.support.MqttHeaders; +import org.springframework.messaging.MessageHeaders; import java.io.IOException; import java.util.Arrays; +import java.util.Optional; /** * @author sean @@ -24,6 +27,9 @@ public class EventsRouter { @Autowired private ObjectMapper mapper; + @Autowired + private IMessageSenderService messageSenderService; + @Bean public IntegrationFlow eventsMethodRouterFlow() { return IntegrationFlows @@ -42,4 +48,21 @@ public class EventsRouter { methodEnum -> mapping.channelMapping(methodEnum, methodEnum.getChannelName()))) .get(); } + + @ServiceActivator(inputChannel = ChannelName.OUTBOUND_EVENTS, outputChannel = ChannelName.OUTBOUND) + public void replyEventsOutbound(CommonTopicReceiver receiver, MessageHeaders headers) { + if (Optional.ofNullable(receiver).map(CommonTopicReceiver::getNeedReply).flatMap(val -> Optional.of(1 != val)).orElse(true)) { + return; + } + messageSenderService.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF, + CommonTopicResponse.builder() + .tid(receiver.getTid()) + .bid(receiver.getBid()) + .method(receiver.getMethod()) + .timestamp(System.currentTimeMillis()) + .data(RequestsReply.success()) + .build()); + + } + } 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 c0115ab..8d8030d 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 @@ -36,13 +36,16 @@ public class ServicesReplyHandler { byte[] payload = (byte[])message.getPayload(); CommonTopicReceiver receiver = mapper.readValue(payload, new TypeReference() {}); + ServiceReply reply; if (LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod().equals(receiver.getMethod())) { LogsFileUploadList list = mapper.convertValue(receiver.getData(), new TypeReference() {}); - receiver.setData(list); + reply = new ServiceReply(); + reply.setResult(list.getResult()); + reply.setOutput(list.getFiles()); } else { - ServiceReply reply = mapper.convertValue(receiver.getData(), new TypeReference() {}); - receiver.setData(reply); + reply = mapper.convertValue(receiver.getData(), new TypeReference() {}); } + receiver.setData(reply); 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/model/ChannelName.java b/src/main/java/com/dji/sample/component/mqtt/model/ChannelName.java index aeee108..cf0ef71 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 @@ -51,6 +51,8 @@ public class ChannelName { public static final String INBOUND_EVENTS = "inboundEvents"; + public static final String OUTBOUND_EVENTS = "outboundEvents"; + public static final String INBOUND_EVENTS_FLIGHT_TASK_PROGRESS = "inboundEventsFlightTaskProgress"; public static final String INBOUND_EVENTS_FILE_UPLOAD_CALLBACK = "inboundEventsFileUploadCallback"; @@ -76,4 +78,14 @@ public class ChannelName { public static final String INBOUND_REQUESTS_CONFIG = "inboundRequestsConfig"; public static final String INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA = "inboundEventsHighestPriorityUploadFlightTaskMedia"; + + public static final String INBOUND_EVENTS_FLIGHT_TASK_READY = "inboundEventsFlightTaskReady"; + + public static final String INBOUND_EVENTS_FLY_TO_POINT_PROGRESS = "inboundFlyToPointProgress"; + + public static final String INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS = "inboundTakeoffToPointProgress"; + + public static final String INBOUND_EVENTS_DRC_STATUS_NOTIFY = "inboundDrcStatusNotify"; + + public static final String INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY = "inboundDrcModeExitNotify"; } diff --git a/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java b/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java index d0782e0..37205bc 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/EventsMethodEnum.java @@ -45,6 +45,16 @@ public enum EventsMethodEnum { HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA("highest_priority_upload_flighttask_media", ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA), + FLIGHT_TASK_READY("flighttask_ready", ChannelName.INBOUND_EVENTS_FLIGHT_TASK_READY), + + FLY_TO_POINT_PROGRESS("fly_to_point_progress", ChannelName.INBOUND_EVENTS_FLY_TO_POINT_PROGRESS), + + TAKE_OFF_TO_POINT_PROGRESS("takeoff_to_point_progress", ChannelName.INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS), + + DRC_STATUS_NOTIFY("drc_status_notify", ChannelName.INBOUND_EVENTS_DRC_STATUS_NOTIFY), + + JOYSTICK_INVALID_NOTIFY("joystick_invalid_notify", ChannelName.INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY), + UNKNOWN("Unknown", ChannelName.DEFAULT); private String method; diff --git a/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java b/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java similarity index 75% rename from src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java rename to src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java index 87667e8..499d85a 100644 --- a/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputReceiver.java +++ b/src/main/java/com/dji/sample/component/mqtt/model/EventsOutputProgressReceiver.java @@ -8,9 +8,11 @@ import lombok.Data; * @date 2022/7/29 */ @Data -public class EventsOutputReceiver { +public class EventsOutputProgressReceiver { private String status; private OutputProgressReceiver progress; + + private T ext; } 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 215995a..9ee3f9a 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 @@ -34,4 +34,9 @@ public class TopicConst { public static final String REGEX_SN = "[A-Za-z0-9]+"; + public static final String DRC = "/drc"; + + public static final String UP = "/up"; + + public static final String DOWN = "/down"; } \ 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 40f7f18..e21816c 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 @@ -2,6 +2,7 @@ package com.dji.sample.component.mqtt.service; import com.dji.sample.component.mqtt.model.CommonTopicResponse; import com.dji.sample.component.mqtt.model.ServiceReply; +import com.fasterxml.jackson.core.type.TypeReference; /** * @author sean.zhou @@ -26,15 +27,16 @@ public interface IMessageSenderService { void publish(String topic, int qos, CommonTopicResponse response); /** - * Send live streaming start message and receive a response at the same time. + * Send message and receive a response at the same time. + * @param clazz * @param topic * @param response notification of whether the start is successful. * @return */ - ServiceReply publishWithReply(String topic, CommonTopicResponse response); + T publishWithReply(Class clazz, String topic, CommonTopicResponse response); /** - * Send live streaming start message and receive a response at the same time. + * Send message and receive a response at the same time. * @param clazz * @param topic * @param response @@ -43,4 +45,46 @@ public interface IMessageSenderService { * @return */ T publishWithReply(Class clazz, String topic, CommonTopicResponse response, int retryTime); + + /** + * Used exclusively for sending messages for services. + * @param clazz The generic class for ServiceReply. + * @param sn + * @param method + * @param data + * @param bid + * @param + * @return + */ + ServiceReply publishServicesTopic(TypeReference clazz, String sn, String method, Object data, String bid); + + /** + * Used exclusively for sending messages for services, and does not set the received subtype. + * @param sn + * @param method + * @param data + * @param bid + * @return + */ + ServiceReply publishServicesTopic(String sn, String method, Object data, String bid); + + /** + * Used exclusively for sending messages for services. + * @param clazz The generic class for ServiceReply. + * @param sn + * @param method + * @param data + * @param + * @return + */ + ServiceReply publishServicesTopic(TypeReference clazz, String sn, String method, Object data); + + /** + * Used exclusively for sending messages for services, and does not set the received subtype. + * @param sn + * @param method + * @param data + * @return + */ + ServiceReply publishServicesTopic(String sn, String method, Object data); } 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 2a76cf5..fdaf62c 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 @@ -1,17 +1,19 @@ package com.dji.sample.component.mqtt.service.impl; -import com.dji.sample.component.mqtt.model.Chan; -import com.dji.sample.component.mqtt.model.CommonTopicReceiver; -import com.dji.sample.component.mqtt.model.CommonTopicResponse; -import com.dji.sample.component.mqtt.model.ServiceReply; +import com.dji.sample.component.mqtt.model.*; import com.dji.sample.component.mqtt.service.IMessageSenderService; import com.dji.sample.component.mqtt.service.IMqttMessageGateway; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import java.util.Objects; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; /** @@ -30,17 +32,12 @@ public class MessageSenderServiceImpl implements IMessageSenderService { private ObjectMapper mapper; public void publish(String topic, CommonTopicResponse response) { - try { - log.info("send topic: {}, payload: {}", topic, response.toString()); - messageGateway.publish(topic, mapper.writeValueAsBytes(response)); - } catch (JsonProcessingException e) { - log.info("Failed to publish the message. {}", response.toString()); - e.printStackTrace(); - } + this.publish(topic, 1, response); } public void publish(String topic, int qos, CommonTopicResponse response) { try { + log.info("send topic: {}, payload: {}", topic, response.toString()); messageGateway.publish(topic, mapper.writeValueAsBytes(response), qos); } catch (JsonProcessingException e) { log.info("Failed to publish the message. {}", response.toString()); @@ -48,12 +45,11 @@ public class MessageSenderServiceImpl implements IMessageSenderService { } } - public ServiceReply publishWithReply(String topic, CommonTopicResponse response) { - return this.publishWithReply(ServiceReply.class, topic, response, 2); + public T publishWithReply(Class clazz, String topic, CommonTopicResponse response) { + return this.publishWithReply(clazz, topic, response, 2); } public T publishWithReply(Class clazz, String topic, CommonTopicResponse response, int retryTime) { - log.info("send topic: {}, payload: {}", topic, response.toString()); AtomicInteger time = new AtomicInteger(0); // Retry three times while (time.getAndIncrement() <= retryTime) { @@ -62,15 +58,59 @@ public class MessageSenderServiceImpl implements IMessageSenderService { Chan> chan = Chan.getInstance(); // If the message is not received in 0.5 seconds then resend it again. CommonTopicReceiver receiver = chan.get(response.getTid()); - if (receiver == null) { - continue; - } + // Need to match tid and bid. - if (receiver.getTid().equals(response.getTid()) && + if (Objects.nonNull(receiver) && receiver.getTid().equals(response.getTid()) && receiver.getBid().equals(response.getBid())) { - return receiver.getData(); + if (clazz.isAssignableFrom(receiver.getData().getClass())) { + return receiver.getData(); + } + throw new TypeMismatchException(receiver.getData(), clazz); } + // It must be guaranteed that the tid and bid of each message are different. + response.setBid(UUID.randomUUID().toString()); + response.setTid(UUID.randomUUID().toString()); } throw new RuntimeException("No message reply received."); } + + @Override + public ServiceReply publishServicesTopic(TypeReference clazz, String sn, String method, Object data, String bid) { + String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + sn + TopicConst.SERVICES_SUF; + ServiceReply reply = this.publishWithReply(ServiceReply.class, topic, + CommonTopicResponse.builder() + .tid(UUID.randomUUID().toString()) + .bid(StringUtils.hasText(bid) ? bid : UUID.randomUUID().toString()) + .timestamp(System.currentTimeMillis()) + .method(method) + .data(Objects.requireNonNullElse(data, "")) + .build()); + if (Objects.isNull(clazz)) { + return reply; + } + // put together in "output" + if (Objects.nonNull(reply.getInfo())) { + reply.setOutput(mapper.convertValue(reply.getInfo(), clazz)); + } + if (Objects.nonNull(reply.getOutput())) { + reply.setOutput(mapper.convertValue(reply.getOutput(), clazz)); + } + return reply; + } + + @Override + public ServiceReply publishServicesTopic(String sn, String method, Object data, String bid) { + return this.publishServicesTopic(null, sn, method, data, bid); + } + + @Override + public ServiceReply publishServicesTopic(TypeReference clazz, String sn, String method, Object data) { + return this.publishServicesTopic(clazz, sn, method, data, null); + } + + @Override + public ServiceReply publishServicesTopic(String sn, String method, Object data) { + return this.publishServicesTopic(null, sn, method, data, null); + } + } \ No newline at end of file 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 7ae29db..cb0ca7a 100644 --- a/src/main/java/com/dji/sample/component/redis/RedisConst.java +++ b/src/main/java/com/dji/sample/component/redis/RedisConst.java @@ -39,6 +39,10 @@ public final class RedisConst { public static final String WAYLINE_JOB_TIMED_EXECUTE = "wayline_job_timed_execute"; + public static final String WAYLINE_JOB_CONDITION_PREPARE = "wayline_job_condition_prepare"; + + public static final String WAYLINE_JOB_CONDITION_PREFIX = WAYLINE_JOB_CONDITION_PREPARE + DELIMITER; + public static final String WAYLINE_JOB_BLOCK_PREFIX = "wayline_job_block" + DELIMITER; public static final String WAYLINE_JOB_RUNNING_PREFIX = "wayline_job_running" + DELIMITER; @@ -53,7 +57,13 @@ public final class RedisConst { public static final String LIVE_CAPACITY = "live_capacity"; + public static final String DRC_PREFIX = "drc" + DELIMITER; + + public static final Integer DRC_MODE_ALIVE_SECOND = 3600; + public static final String MQTT_ACL_PREFIX = "mqtt_acl" + DELIMITER; public static final String FILE_UPLOADING_PREFIX = "file_uploading" + DELIMITER; -} + + public static final String DRONE_CONTROL_PREFiX = "control_source" + DELIMITER; +} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java index 76727ca..2094d48 100644 --- a/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java +++ b/src/main/java/com/dji/sample/component/websocket/model/BizCodeEnum.java @@ -57,7 +57,22 @@ public enum BizCodeEnum { FILE_UPLOAD_CALLBACK("file_upload_callback"), - HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA("HIGHEST_PRIORITY_UPLOAD_FLIGHTTASK_MEDIA"); + FILE_UPLOAD_PROGRESS("fileupload_progress"), + + OTA_PROGRESS("ota_progress"), + + HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA("highest_priority_upload_flighttask_media"), + + CONTROL_SOURCE_CHANGE("control_source_change"), + + FLY_TO_POINT_PROGRESS("fly_to_point_progress"), + + TAKE_OFF_TO_POINT_PROGRESS("takeoff_to_point_progress"), + + DRC_STATUS_NOTIFY("drc_status_notify"), + + JOYSTICK_INVALID_NOTIFY("joystick_invalid_notify") + ; private String code; diff --git a/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java index e1d67e7..a9b3d8f 100644 --- a/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java +++ b/src/main/java/com/dji/sample/component/websocket/service/ISendMessageService.java @@ -25,4 +25,8 @@ public interface ISendMessageService { * @param message message */ void sendBatch(Collection sessions, CustomWebSocketMessage message); + + void sendBatch(String workspaceId, Integer userType, String bizCode, Object data); + + void sendBatch(String workspaceId, String bizCode, Object data); } diff --git a/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java index 0546728..b605cf9 100644 --- a/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java +++ b/src/main/java/com/dji/sample/component/websocket/service/impl/SendMessageServiceImpl.java @@ -3,14 +3,17 @@ package com.dji.sample.component.websocket.service.impl; import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; 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.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.springframework.web.socket.TextMessage; import java.io.IOException; import java.util.Collection; +import java.util.Objects; /** * @author sean.zhou @@ -24,6 +27,9 @@ public class SendMessageServiceImpl implements ISendMessageService { @Autowired private ObjectMapper mapper; + @Autowired + private IWebSocketManageService webSocketManageService; + @Override public void sendMessage(ConcurrentWebSocketSession session, CustomWebSocketMessage message) { if (session == null) { @@ -70,4 +76,25 @@ public class SendMessageServiceImpl implements ISendMessageService { e.printStackTrace(); } } + + @Override + public void sendBatch(String workspaceId, Integer userType, String bizCode, Object data) { + if (!StringUtils.hasText(workspaceId)) { + throw new RuntimeException("Workspace ID does not exist."); + } + Collection sessions = Objects.isNull(userType) ? + webSocketManageService.getValueWithWorkspace(workspaceId) : + webSocketManageService.getValueWithWorkspaceAndUserType(workspaceId, userType); + + this.sendBatch(sessions, CustomWebSocketMessage.builder() + .data(data) + .timestamp(System.currentTimeMillis()) + .bizCode(bizCode) + .build()); + } + + @Override + public void sendBatch(String workspaceId, String bizCode, Object data) { + this.sendBatch(workspaceId, null, bizCode, data); + } } \ No newline at end of file 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 7836e5f..53d07f5 100644 --- a/src/main/java/com/dji/sample/control/controller/DockController.java +++ b/src/main/java/com/dji/sample/control/controller/DockController.java @@ -1,12 +1,15 @@ 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.model.enums.DroneAuthorityEnum; +import com.dji.sample.control.model.param.*; 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.*; +import javax.validation.Valid; + /** * @author sean * @version 1.2 @@ -24,6 +27,39 @@ public class DockController { public ResponseResult createControlJob(@PathVariable String sn, @PathVariable("service_identifier") String serviceIdentifier, @RequestBody(required = false) RemoteDebugParam param) { - return controlService.controlDock(sn, serviceIdentifier, param); + return controlService.controlDockDebug(sn, serviceIdentifier, param); + } + + @PostMapping("/{sn}/jobs/fly-to-point") + public ResponseResult flyToPoint(@PathVariable String sn, @Valid @RequestBody FlyToPointParam param) { + return controlService.flyToPoint(sn, param); + } + + @DeleteMapping("/{sn}/jobs/fly-to-point") + public ResponseResult flyToPointStop(@PathVariable String sn) { + return controlService.flyToPointStop(sn); + } + + @PostMapping("/{sn}/jobs/takeoff-to-point") + public ResponseResult takeoffToPoint(@PathVariable String sn, @Valid @RequestBody TakeoffToPointParam param) { + return controlService.takeoffToPoint(sn, param); } + + @PostMapping("/{sn}/authority/flight") + public ResponseResult seizeFlightAuthority(@PathVariable String sn) { + return controlService.seizeAuthority(sn, DroneAuthorityEnum.FLIGHT, null); + } + + @PostMapping("/{sn}/authority/payload") + public ResponseResult seizePayloadAuthority(@PathVariable String sn, @Valid @RequestBody DronePayloadParam param) { + return controlService.seizeAuthority(sn, DroneAuthorityEnum.PAYLOAD, param); + } + + @PostMapping("/{sn}/payload/commands") + public ResponseResult payloadCommands(@PathVariable String sn, @Valid @RequestBody PayloadCommandsParam param) throws Exception { + param.setSn(sn); + return controlService.payloadCommands(param); + } + + } diff --git a/src/main/java/com/dji/sample/control/controller/DrcController.java b/src/main/java/com/dji/sample/control/controller/DrcController.java new file mode 100644 index 0000000..6b66089 --- /dev/null +++ b/src/main/java/com/dji/sample/control/controller/DrcController.java @@ -0,0 +1,55 @@ +package com.dji.sample.control.controller; + +import com.dji.sample.common.model.CustomClaim; +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.control.model.dto.JwtAclDTO; +import com.dji.sample.control.model.dto.MqttBrokerDTO; +import com.dji.sample.control.model.param.DrcConnectParam; +import com.dji.sample.control.model.param.DrcModeParam; +import com.dji.sample.control.service.IDrcService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +@RestController +@Slf4j +@RequestMapping("${url.control.prefix}${url.control.version}") +public class DrcController { + + @Autowired + private IDrcService drcService; + + @PostMapping("/workspaces/{workspace_id}/drc/connect") + public ResponseResult drcConnect(@PathVariable("workspace_id") String workspaceId, HttpServletRequest request, @Valid @RequestBody DrcConnectParam param) { + CustomClaim claims = (CustomClaim) request.getAttribute(TOKEN_CLAIM); + + MqttBrokerDTO brokerDTO = drcService.userDrcAuth(workspaceId, claims.getId(), claims.getUsername(), param); + return ResponseResult.success(brokerDTO); + } + + @PostMapping("/workspaces/{workspace_id}/drc/enter") + public ResponseResult drcEnter(@PathVariable("workspace_id") String workspaceId, @Valid @RequestBody DrcModeParam param) { + JwtAclDTO acl = drcService.deviceDrcEnter(workspaceId, param); + + return ResponseResult.success(acl); + } + + @PostMapping("/workspaces/{workspace_id}/drc/exit") + public ResponseResult drcExit(@PathVariable("workspace_id") String workspaceId, @Valid @RequestBody DrcModeParam param) { + drcService.deviceDrcExit(workspaceId, param); + + return ResponseResult.success(); + } + + +} diff --git a/src/main/java/com/dji/sample/control/model/dto/AlarmState.java b/src/main/java/com/dji/sample/control/model/dto/AlarmState.java index f670415..1fb590b 100644 --- a/src/main/java/com/dji/sample/control/model/dto/AlarmState.java +++ b/src/main/java/com/dji/sample/control/model/dto/AlarmState.java @@ -1,7 +1,7 @@ package com.dji.sample.control.model.dto; +import com.dji.sample.control.service.impl.RemoteDebugHandler; import com.dji.sample.manage.model.enums.StateSwitchEnum; -import com.dji.sample.manage.model.receiver.BasicDeviceProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -18,7 +18,7 @@ import java.util.Objects; @Data @AllArgsConstructor @NoArgsConstructor -public class AlarmState extends BasicDeviceProperty { +public class AlarmState extends RemoteDebugHandler { private Integer action; 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 index fa76703..9656a9e 100644 --- a/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java +++ b/src/main/java/com/dji/sample/control/model/dto/BatteryStoreMode.java @@ -1,7 +1,7 @@ package com.dji.sample.control.model.dto; import com.dji.sample.control.model.enums.BatteryStoreModeEnum; -import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import com.dji.sample.control.service.impl.RemoteDebugHandler; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -18,7 +18,7 @@ import java.util.Objects; @Data @AllArgsConstructor @NoArgsConstructor -public class BatteryStoreMode extends BasicDeviceProperty { +public class BatteryStoreMode extends RemoteDebugHandler { private Integer action; diff --git a/src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java b/src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java new file mode 100644 index 0000000..771e85a --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/DrcModeDTO.java @@ -0,0 +1,32 @@ +package com.dji.sample.control.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DrcModeDTO { + + private MqttBrokerDTO mqttBroker; + + /** + * range: 1 - 30 + */ + @Builder.Default + private Integer osdFrequency = 10; + + /** + * range: 1 - 30 + */ + @Builder.Default + private Integer hsiFrequency = 1; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java b/src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java new file mode 100644 index 0000000..26429a9 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/DrcModeReasonReceiver.java @@ -0,0 +1,15 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.control.model.enums.DrcModeReasonEnum; +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/14 + */ +@Data +public class DrcModeReasonReceiver { + + private DrcModeReasonEnum reason; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java b/src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java new file mode 100644 index 0000000..1ef8c04 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/DrcStatusNotifyReceiver.java @@ -0,0 +1,18 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.control.model.enums.DrcStatusErrorEnum; +import com.dji.sample.manage.model.enums.DockDrcStateEnum; +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/17 + */ +@Data +public class DrcStatusNotifyReceiver { + + private DrcStatusErrorEnum result; + + private DockDrcStateEnum drcState; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java b/src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java new file mode 100644 index 0000000..f51fbf6 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/FlyToProgressReceiver.java @@ -0,0 +1,22 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.control.model.enums.FlyToStatusEnum; +import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum; +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/14 + */ +@Data +public class FlyToProgressReceiver { + + private WaylineErrorCodeEnum result; + + private FlyToStatusEnum status; + + private String flyToId; + + private Integer wayPointIndex; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java b/src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java new file mode 100644 index 0000000..9bc6959 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/JwtAclDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.control.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class JwtAclDTO { + + private List sub; + + private List pub; + + private List all; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java b/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java index 00cb354..884330d 100644 --- a/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java +++ b/src/main/java/com/dji/sample/control/model/dto/LinkWorkMode.java @@ -1,7 +1,7 @@ package com.dji.sample.control.model.dto; import com.dji.sample.control.model.enums.LinkWorkModeEnum; -import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import com.dji.sample.control.service.impl.RemoteDebugHandler; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; @@ -19,7 +19,7 @@ import java.util.Objects; @Data @AllArgsConstructor @NoArgsConstructor -public class LinkWorkMode extends BasicDeviceProperty { +public class LinkWorkMode extends RemoteDebugHandler { @JsonProperty("link_workmode") private Integer linkWorkMode; diff --git a/src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java b/src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java new file mode 100644 index 0000000..af19dba --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/MqttBrokerDTO.java @@ -0,0 +1,31 @@ +package com.dji.sample.control.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MqttBrokerDTO { + + private String address; + + private String username; + + private String password; + + private String clientId; + + private Long expireTime; + + @Builder.Default + private Boolean enableTls = false; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/PointDTO.java b/src/main/java/com/dji/sample/control/model/dto/PointDTO.java new file mode 100644 index 0000000..8c6acaf --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/PointDTO.java @@ -0,0 +1,31 @@ +package com.dji.sample.control.model.dto; + +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotNull; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/14 + */ +@Data +public class PointDTO { + + @Range(min = -90, max = 90) + @NotNull + private Double latitude; + + @NotNull + @Range(min = -180, max = 180) + private Double longitude; + + /** + * WGS84 + * The M30 series are ellipsoidal heights. + */ + @NotNull + @Range(min = 2, max = 1500) + private Double height; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java b/src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java new file mode 100644 index 0000000..069307d --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/RemoteDebugOpenState.java @@ -0,0 +1,25 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.common.util.SpringBeanUtils; +import com.dji.sample.control.service.impl.RemoteDebugHandler; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; +import com.dji.sample.manage.service.IDeviceService; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RemoteDebugOpenState extends RemoteDebugHandler { + + @Override + public boolean canPublish(String sn) { + IDeviceService deviceService = SpringBeanUtils.getBean(IDeviceService.class); + DockModeCodeEnum dockMode = deviceService.getDockMode(sn); + return DockModeCodeEnum.IDLE == dockMode; + } +} diff --git a/src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java b/src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java new file mode 100644 index 0000000..0de9461 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/ResultNotifyDTO.java @@ -0,0 +1,24 @@ +package com.dji.sample.control.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ResultNotifyDTO { + + private Integer result; + + private String message; + + private String sn; +} diff --git a/src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java b/src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java new file mode 100644 index 0000000..66957ab --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/ReturnHomeState.java @@ -0,0 +1,27 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.common.util.SpringBeanUtils; +import com.dji.sample.control.service.impl.RemoteDebugHandler; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver; +import com.dji.sample.manage.service.IDeviceRedisService; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/19 + */ + +public class ReturnHomeState extends RemoteDebugHandler { + + @Override + public boolean canPublish(String sn) { + IDeviceRedisService deviceRedisService = SpringBeanUtils.getBean(IDeviceRedisService.class); + return deviceRedisService.getDeviceOnline(sn) + .map(DeviceDTO::getChildDeviceSn) + .flatMap(deviceSn -> deviceRedisService.getDeviceOsd(deviceSn, OsdSubDeviceReceiver.class)) + .map(OsdSubDeviceReceiver::getElevation) + .map(elevation -> elevation > 0) + .orElse(false); + } +} diff --git a/src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java b/src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java new file mode 100644 index 0000000..e4f5a83 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/dto/TakeoffProgressReceiver.java @@ -0,0 +1,25 @@ +package com.dji.sample.control.model.dto; + +import com.dji.sample.control.model.enums.TakeoffStatusEnum; +import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum; +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/14 + */ +@Data +public class TakeoffProgressReceiver { + + private WaylineErrorCodeEnum result; + + private TakeoffStatusEnum status; + + private String flightId; + + private String trackId; + + private Integer wayPointIndex; + +} diff --git a/src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java b/src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java new file mode 100644 index 0000000..a6dfe3c --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/CameraModeEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/3 + */ +public enum CameraModeEnum { + + PHOTO, VIDEO; + + @JsonValue + public int getVal() { + return ordinal(); + } + + @JsonCreator + public static CameraModeEnum find(int val) { + return Arrays.stream(values()).filter(modeEnum -> modeEnum.ordinal() == val).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java b/src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java new file mode 100644 index 0000000..fbe9f1a --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/CameraStateEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public enum CameraStateEnum { + + IDLE, WORKING; + + @JsonValue + public int getVal() { + return ordinal(); + } + + @JsonCreator + public static CameraStateEnum find(int val) { + return Arrays.stream(values()).filter(stateEnum -> stateEnum.ordinal() == val).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java b/src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java new file mode 100644 index 0000000..cda43e1 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/CameraTypeEnum.java @@ -0,0 +1,38 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/3 + */ +@Getter +public enum CameraTypeEnum { + + ZOOM("zoom"), + + WIDE("wide"), + + IR("ir"); + + String type; + + CameraTypeEnum(String type) { + this.type = type; + } + + @JsonValue + public String getType() { + return type; + } + + @JsonCreator + public static CameraTypeEnum find(String cameraType) { + return Arrays.stream(CameraTypeEnum.values()).filter(typeEnum -> typeEnum.type.equals(cameraType)).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java new file mode 100644 index 0000000..93697d7 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/DrcMethodEnum.java @@ -0,0 +1,22 @@ +package com.dji.sample.control.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +@Getter +public enum DrcMethodEnum { + + DRC_MODE_ENTER("drc_mode_enter"), + + DRC_MODE_EXIT("drc_mode_exit"); + + String method; + + DrcMethodEnum(String method) { + this.method = method; + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java b/src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java new file mode 100644 index 0000000..ceb20ac --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/DrcModeReasonEnum.java @@ -0,0 +1,47 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/14 + */ +public enum DrcModeReasonEnum { + + UNKNOWN(-1, "unknown"), + + RC_LOST(0, "The remote controller is lost."), + + BATTERY_LOW_GO_HOME(1, "Due to low battery, the drone automatically returned home."), + + BATTERY_SUPER_LOW_LANDING(2, "Due to the serious low battery, the drone landed automatically."), + + NEAR_BOUNDARY(3, "The drone is near a not-fly zone."), + + RC_AUTHORITY(4, "The remote controller grabs control authority."); + + int val; + + String message; + + DrcModeReasonEnum(int val, String message) { + this.val = val; + this.message = message; + } + + public int getVal() { + return val; + } + + public String getMessage() { + return message; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DrcModeReasonEnum find(int val) { + return Arrays.stream(values()).filter(reasonEnum -> reasonEnum.val == val).findAny().orElse(UNKNOWN); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java b/src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java new file mode 100644 index 0000000..1cee634 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/DrcStatusErrorEnum.java @@ -0,0 +1,52 @@ +package com.dji.sample.control.model.enums; + +import com.dji.sample.common.error.IErrorInfo; +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/17 + */ +public enum DrcStatusErrorEnum implements IErrorInfo { + + SUCCESS(0, "success"), + + MQTT_ERR(514300, "The mqtt connection error."), + + HEARTBEAT_TIMEOUT(514301, "The heartbeat times out and the dock disconnects."), + + MQTT_CERTIFICATE_ERR(514302, "The mqtt certificate is abnormal and the connection fails."), + + MQTT_LOST(514303, "The dock network is abnormal and the mqtt connection is lost."), + + MQTT_REFUSE(514304, "The dock connection to mqtt server was refused."), + + UNKNOWN(-1, "Unknown"); + + String msg; + + int code; + + DrcStatusErrorEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public String getErrorMsg() { + return msg; + } + + @Override + public Integer getErrorCode() { + return code; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DrcStatusErrorEnum find(int code) { + return Arrays.stream(values()).filter(error -> error.code == code).findAny().orElse(UNKNOWN); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java b/src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java new file mode 100644 index 0000000..e751f7e --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/DroneAuthorityEnum.java @@ -0,0 +1,25 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/1 + */ +public enum DroneAuthorityEnum { + + FLIGHT(1), PAYLOAD(2); + + int val; + + DroneAuthorityEnum(int val) { + this.val = val; + } + + @JsonValue + public int getVal() { + return val; + } + +} diff --git a/src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java new file mode 100644 index 0000000..dcb6a21 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/DroneControlMethodEnum.java @@ -0,0 +1,28 @@ +package com.dji.sample.control.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/21 + */ +@Getter +public enum DroneControlMethodEnum { + + FLIGHT_AUTHORITY_GRAB("flight_authority_grab"), + + PAYLOAD_AUTHORITY_GRAB("payload_authority_grab"), + + FLY_TO_POINT("fly_to_point"), + + FLY_TO_POINT_STOP("fly_to_point_stop"), + + TAKE_OFF_TO_POINT("takeoff_to_point"); + + String method; + + DroneControlMethodEnum(String method) { + this.method = method; + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java b/src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java new file mode 100644 index 0000000..4291769 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/FlyToStatusEnum.java @@ -0,0 +1,39 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/14 + */ +public enum FlyToStatusEnum { + + WAYLINE_PROGRESS("wayline_progress", "The FlyTo job is in progress."), + + WAYLINE_FAILED("wayline_failed", "The Fly To task execution failed."), + + WAYLINE_OK("wayline_ok", "The FlyTo job executed successfully."), + + WAYLINE_CANCEL("wayline_cancel", "The FlyTo job is closed."); + + String status; + + String message; + + FlyToStatusEnum(String status, String message) { + this.status = status; + this.message = message; + } + + public String getMessage() { + return message; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static FlyToStatusEnum find(String status) { + return Arrays.stream(values()).filter(statusEnum -> statusEnum.status.equals(status)).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java b/src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java new file mode 100644 index 0000000..2c7e9dd --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/GimbalResetModeEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/13 + */ +public enum GimbalResetModeEnum { + + RECENTER, DOWN, RECENTER_PAN, PITCH_DOWN; + + @JsonValue + public int getVal() { + return ordinal(); + } + + @JsonCreator + public static GimbalResetModeEnum find(int value) { + return Arrays.stream(values()).filter(resetModeEnum -> resetModeEnum.ordinal() == value).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java b/src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java new file mode 100644 index 0000000..771e4dc --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/MqttAclAccessEnum.java @@ -0,0 +1,24 @@ +package com.dji.sample.control.model.enums; + +import lombok.Getter; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/13 + */ +@Getter +public enum MqttAclAccessEnum { + + SUB(1), + + PUB(2), + + ALL(3); + + int value; + + MqttAclAccessEnum(int value) { + this.value = value; + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java b/src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java new file mode 100644 index 0000000..1f1241f --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/PayloadCommandsEnum.java @@ -0,0 +1,52 @@ +package com.dji.sample.control.model.enums; + +import com.dji.sample.control.service.impl.*; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/2 + */ +public enum PayloadCommandsEnum { + + CAMERA_MODE_SWitCH("camera_mode_switch", CameraModeSwitchImpl.class), + + CAMERA_PHOTO_TAKE("camera_photo_take", CameraPhotoTakeImpl.class), + + CAMERA_RECORDING_START("camera_recording_start", CameraRecordingStartImpl.class), + + CAMERA_RECORDING_STOP("camera_recording_stop", CameraRecordingStopImpl.class), + + CAMERA_AIM("camera_aim", CameraAimImpl.class), + + CAMERA_FOCAL_LENGTH_SET("camera_focal_length_set", CameraFocalLengthSetImpl.class), + + GIMBAL_RESET("gimbal_reset", GimbalResetImpl.class); + + String cmd; + + Class clazz; + + PayloadCommandsEnum(String cmd, Class clazz) { + this.cmd = cmd; + this.clazz = clazz; + } + + @JsonValue + public String getCmd() { + return cmd; + } + + public Class getClazz() { + return clazz; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static PayloadCommandsEnum find(String cmd) { + return Arrays.stream(values()).filter(cmdEnum -> cmdEnum.cmd.equals(cmd)).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java b/src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java similarity index 70% rename from src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java rename to src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java index 130239c..3bfed6e 100644 --- a/src/main/java/com/dji/sample/control/model/enums/RemoteControlMethodEnum.java +++ b/src/main/java/com/dji/sample/control/model/enums/RemoteDebugMethodEnum.java @@ -1,9 +1,7 @@ package com.dji.sample.control.model.enums; -import com.dji.sample.control.model.dto.AlarmState; -import com.dji.sample.control.model.dto.BatteryStoreMode; -import com.dji.sample.control.model.dto.LinkWorkMode; -import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import com.dji.sample.control.model.dto.*; +import com.dji.sample.control.service.impl.RemoteDebugHandler; import lombok.Getter; import java.util.Arrays; @@ -14,9 +12,9 @@ import java.util.Arrays; * @date 2022/11/14 */ @Getter -public enum RemoteControlMethodEnum { +public enum RemoteDebugMethodEnum { - DEBUG_MODE_OPEN("debug_mode_open", false, null), + DEBUG_MODE_OPEN("debug_mode_open", false, RemoteDebugOpenState.class), DEBUG_MODE_CLOSE("debug_mode_close", false, null), @@ -24,7 +22,7 @@ public enum RemoteControlMethodEnum { SUPPLEMENT_LIGHT_CLOSE("supplement_light_close", false, null), - RETURN_HOME("return_home", false, null), + RETURN_HOME("return_home", false, ReturnHomeState.class), DEVICE_REBOOT("device_reboot", true, null), @@ -64,16 +62,16 @@ public enum RemoteControlMethodEnum { private Boolean progress; - private Class clazz; + private Class clazz; - RemoteControlMethodEnum(String method, Boolean progress, Class clazz) { + RemoteDebugMethodEnum(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()) + public static RemoteDebugMethodEnum find(String method) { + return Arrays.stream(RemoteDebugMethodEnum.values()) .filter(methodEnum -> methodEnum.method.equals(method)) .findAny() .orElse(UNKNOWN); diff --git a/src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java b/src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java new file mode 100644 index 0000000..94ae3de --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/enums/TakeoffStatusEnum.java @@ -0,0 +1,47 @@ +package com.dji.sample.control.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/17 + */ +public enum TakeoffStatusEnum { + + TASK_READY("task_ready", "The drone is preparing to take off."), + + WAYLINE_PROGRESS("wayline_progress", "The drone is taking off."), + + WAYLINE_FAILED("wayline_failed", "The drone failed to take off."), + + WAYLINE_OK("wayline_ok", "The drone took off successfully."), + + WAYLINE_CANCEL("wayline_cancel", "The drone takeoff job has been cancelled."), + + TASK_FINISH("task_finish", "The drone takeoff job is completed."); + + String status; + + String message; + + TakeoffStatusEnum(String status, String message) { + this.status = status; + this.message = message; + } + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static TakeoffStatusEnum find(String status) { + return Arrays.stream(values()).filter(statusEnum -> statusEnum.status.equals(status)).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java b/src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java new file mode 100644 index 0000000..05930f0 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/DeviceDrcInfoParam.java @@ -0,0 +1,19 @@ +package com.dji.sample.control.model.param; + +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/2 + */ +@Data +public class DeviceDrcInfoParam { + + @Range(min = 1, max = 30) + private Integer osdFrequency = 10; + + @Range(min = 1, max = 30) + private Integer hsiFrequency = 1; +} diff --git a/src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java b/src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java new file mode 100644 index 0000000..3aa7926 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/DrcConnectParam.java @@ -0,0 +1,19 @@ +package com.dji.sample.control.model.param; + +import com.dji.sample.component.redis.RedisConst; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +@Data +public class DrcConnectParam { + + private String clientId; + + @Range(min = 1800, max = 86400) + private long expireSec = RedisConst.DRC_MODE_ALIVE_SECOND; +} diff --git a/src/main/java/com/dji/sample/control/model/param/DrcModeParam.java b/src/main/java/com/dji/sample/control/model/param/DrcModeParam.java new file mode 100644 index 0000000..a750dc5 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/DrcModeParam.java @@ -0,0 +1,35 @@ +package com.dji.sample.control.model.param; + +import com.dji.sample.component.redis.RedisConst; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Range; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DrcModeParam { + + @NotBlank + private String clientId; + + @NotBlank + private String dockSn; + + @Range(min = 1800, max = 86400) + private long expireSec = RedisConst.DRC_MODE_ALIVE_SECOND; + + @Valid + private DeviceDrcInfoParam deviceInfo = new DeviceDrcInfoParam(); +} diff --git a/src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java b/src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java new file mode 100644 index 0000000..97d156e --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/DronePayloadParam.java @@ -0,0 +1,54 @@ +package com.dji.sample.control.model.param; + +import com.dji.sample.control.model.enums.CameraModeEnum; +import com.dji.sample.control.model.enums.CameraTypeEnum; +import com.dji.sample.control.model.enums.GimbalResetModeEnum; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/1 + */ +@Data +public class DronePayloadParam { + + @Pattern(regexp = "\\d+-\\d+-\\d+") + @NotNull + private String payloadIndex; + + private CameraTypeEnum cameraType; + + @Range(min = 2, max = 200) + private Float zoomFactor; + + private CameraModeEnum cameraMode; + + /** + * true: Lock the gimbal, the gimbal and the drone rotate together. + * false: Only the gimbal rotates, but the drone does not. + */ + private Boolean locked; + + private Double pitchSpeed; + + /** + * Only valid when locked is false. + */ + private Double yawSpeed; + + /** + * upper left corner as center point + */ + @Range(min = 0, max = 1) + private Double x; + + @Range(min = 0, max = 1) + private Double y; + + private GimbalResetModeEnum resetMode; +} diff --git a/src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java b/src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java new file mode 100644 index 0000000..98b03e1 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/FlyToPointParam.java @@ -0,0 +1,37 @@ +package com.dji.sample.control.model.param; + +import com.dji.sample.control.model.dto.PointDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Range; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.List; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/14 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FlyToPointParam { + + private String flyToId; + + @Range(min = 1, max = 15) + @NotNull + private Integer maxSpeed; + + /** + * The M30 series only support one point. + */ + @Size(min = 1) + @Valid + @NotNull + private List points; +} diff --git a/src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java b/src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java new file mode 100644 index 0000000..f5c4a6e --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/PayloadCommandsParam.java @@ -0,0 +1,27 @@ +package com.dji.sample.control.model.param; + +import com.dji.sample.control.model.enums.PayloadCommandsEnum; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/2 + */ +@Data +public class PayloadCommandsParam { + + private String sn; + + @NotNull + @Valid + private PayloadCommandsEnum cmd; + + @Valid + @NotNull + private DronePayloadParam data; + +} diff --git a/src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java b/src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java new file mode 100644 index 0000000..4de1273 --- /dev/null +++ b/src/main/java/com/dji/sample/control/model/param/TakeoffToPointParam.java @@ -0,0 +1,49 @@ +package com.dji.sample.control.model.param; + +import com.dji.sample.manage.model.enums.DroneRcLostActionEnum; +import com.dji.sample.manage.model.enums.WaylineRcLostActionEnum; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotNull; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/1 + */ +@Data +public class TakeoffToPointParam { + + private String flightId; + + @Range(min = -180, max = 180) + @NotNull + private Double targetLongitude; + + @Range(min = -90, max = 90) + @NotNull + private Double targetLatitude; + + @Range(min = 2, max = 1500) + @NotNull + private Double targetHeight; + + @Range(min = 2, max = 1500) + @NotNull + private Double securityTakeoffHeight; + + @Range(min = 2, max = 1500) + @NotNull + private Double rthAltitude; + + @NotNull + private DroneRcLostActionEnum rcLostAction; + + @NotNull + private WaylineRcLostActionEnum exitWaylineWhenRcLost; + + @Range(min = 1, max = 15) + @NotNull + private Double maxSpeed; +} 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 f04c138..f9e122b 100644 --- a/src/main/java/com/dji/sample/control/service/IControlService.java +++ b/src/main/java/com/dji/sample/control/service/IControlService.java @@ -2,7 +2,8 @@ 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 com.dji.sample.control.model.enums.DroneAuthorityEnum; +import com.dji.sample.control.model.param.*; import org.springframework.messaging.MessageHeaders; /** @@ -19,13 +20,60 @@ public interface IControlService { * @param param * @return */ - ResponseResult controlDock(String sn, String serviceIdentifier, RemoteDebugParam param); + ResponseResult controlDockDebug(String sn, String serviceIdentifier, RemoteDebugParam param); /** - * Handles multi-state command progress information. + * Make the drone fly to the target point. + * @param sn + * @param param + * @return + */ + ResponseResult flyToPoint(String sn, FlyToPointParam param); + + /** + * End the mission of flying the drone to the target point. + * @param sn + * @return + */ + ResponseResult flyToPointStop(String sn); + + /** + * Handle progress result notifications for fly to target point. + * @param receiver + * @param headers + * @return + */ + CommonTopicReceiver handleFlyToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers); + + /** + * Control the drone to take off. + * @param sn + * @param param + * @return + */ + ResponseResult takeoffToPoint(String sn, TakeoffToPointParam param); + + /** + * Handle progress result notifications for takeoff to target point. * @param receiver * @param headers + * @return */ - void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers); + CommonTopicReceiver handleTakeoffToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers); + /** + * Seize the control authority of the drone or the payload control authority. + * @param sn + * @param authority + * @param param + * @return + */ + ResponseResult seizeAuthority(String sn, DroneAuthorityEnum authority, DronePayloadParam param); + + /** + * Control the payload of the drone. + * @param param + * @return + */ + ResponseResult payloadCommands(PayloadCommandsParam param) throws Exception; } diff --git a/src/main/java/com/dji/sample/control/service/IDrcService.java b/src/main/java/com/dji/sample/control/service/IDrcService.java new file mode 100644 index 0000000..505f526 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/IDrcService.java @@ -0,0 +1,60 @@ +package com.dji.sample.control.service; + +import com.dji.sample.control.model.dto.JwtAclDTO; +import com.dji.sample.control.model.dto.MqttBrokerDTO; +import com.dji.sample.control.model.param.DrcConnectParam; +import com.dji.sample.control.model.param.DrcModeParam; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +public interface IDrcService { + + /** + * Save the drc mode of dock in redis. + * @param dockSn + * @param clientId + */ + void setDrcModeInRedis(String dockSn, String clientId); + + /** + * Query the client that is controlling the dock. + * @param dockSn + * @return clientId + */ + String getDrcModeInRedis(String dockSn); + + /** + * Delete the drc mode of dock in redis. + * @param dockSn + * @return + */ + Boolean delDrcModeInRedis(String dockSn); + + /** + * Provide mqtt options for the control terminal. + * @param workspaceId + * @param userId + * @param username + * @param param + * @return + */ + MqttBrokerDTO userDrcAuth(String workspaceId, String userId, String username, DrcConnectParam param); + + /** + * Make the dock enter drc mode. And grant relevant permissions. + * @param workspaceId + * @param param + * @return + */ + JwtAclDTO deviceDrcEnter(String workspaceId, DrcModeParam param); + + /** + * Make the dock exit drc mode. + * @param workspaceId + * @param param + */ + void deviceDrcExit(String workspaceId, DrcModeParam param); +} diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java new file mode 100644 index 0000000..4afb448 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/CameraAimImpl.java @@ -0,0 +1,24 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.param.DronePayloadParam; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class CameraAimImpl extends PayloadCommandsHandler { + + public CameraAimImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean valid() { + return Objects.nonNull(param.getX()) && Objects.nonNull(param.getY()) + && Objects.nonNull(param.getLocked()) && Objects.nonNull(param.getCameraType()); + } + +} diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java new file mode 100644 index 0000000..d8b1450 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/CameraFocalLengthSetImpl.java @@ -0,0 +1,41 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.enums.CameraStateEnum; +import com.dji.sample.control.model.enums.CameraTypeEnum; +import com.dji.sample.control.model.param.DronePayloadParam; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class CameraFocalLengthSetImpl extends PayloadCommandsHandler { + + public CameraFocalLengthSetImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean valid() { + return Objects.nonNull(param.getCameraType()) && Objects.nonNull(param.getZoomFactor()) + && (CameraTypeEnum.ZOOM == param.getCameraType() + || CameraTypeEnum.IR == param.getCameraType()); + } + + @Override + public boolean canPublish(String deviceSn) { + super.canPublish(deviceSn); + if (CameraStateEnum.WORKING == osdCamera.getPhotoState()) { + return false; + } + switch (param.getCameraType()) { + case IR: + return param.getZoomFactor().intValue() != osdCamera.getIrZoomFactor(); + case ZOOM: + return param.getZoomFactor().intValue() != osdCamera.getZoomFactor(); + } + return false; + } +} diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java new file mode 100644 index 0000000..b20dbf0 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/CameraModeSwitchImpl.java @@ -0,0 +1,31 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.enums.CameraStateEnum; +import com.dji.sample.control.model.param.DronePayloadParam; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class CameraModeSwitchImpl extends PayloadCommandsHandler { + + public CameraModeSwitchImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean valid() { + return Objects.nonNull(param.getCameraMode()); + } + + @Override + public boolean canPublish(String deviceSn) { + super.canPublish(deviceSn); + return param.getCameraMode() != osdCamera.getCameraMode() + && CameraStateEnum.IDLE == osdCamera.getPhotoState() + && CameraStateEnum.IDLE == osdCamera.getRecordingState(); + } +} diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java new file mode 100644 index 0000000..79034b3 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/CameraPhotoTakeImpl.java @@ -0,0 +1,22 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.enums.CameraStateEnum; +import com.dji.sample.control.model.param.DronePayloadParam; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class CameraPhotoTakeImpl extends PayloadCommandsHandler { + + public CameraPhotoTakeImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean canPublish(String deviceSn) { + super.canPublish(deviceSn); + return CameraStateEnum.WORKING != osdCamera.getPhotoState() && osdCamera.getRemainPhotoNum() > 0; + } +} diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java new file mode 100644 index 0000000..afe7f79 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStartImpl.java @@ -0,0 +1,25 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.enums.CameraModeEnum; +import com.dji.sample.control.model.enums.CameraStateEnum; +import com.dji.sample.control.model.param.DronePayloadParam; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class CameraRecordingStartImpl extends PayloadCommandsHandler { + + public CameraRecordingStartImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean canPublish(String deviceSn) { + super.canPublish(deviceSn); + return CameraModeEnum.VIDEO == osdCamera.getCameraMode() + && CameraStateEnum.IDLE == osdCamera.getRecordingState() + && osdCamera.getRemainRecordDuration() > 0; + } +} diff --git a/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java new file mode 100644 index 0000000..929816b --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/CameraRecordingStopImpl.java @@ -0,0 +1,22 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.enums.CameraStateEnum; +import com.dji.sample.control.model.param.DronePayloadParam; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class CameraRecordingStopImpl extends PayloadCommandsHandler { + + public CameraRecordingStopImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean canPublish(String deviceSn) { + super.canPublish(deviceSn); + return CameraStateEnum.WORKING == osdCamera.getRecordingState(); + } +} 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 beb1e29..d9d43a7 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 @@ -6,26 +6,34 @@ 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.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.BizCodeEnum; 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.model.dto.FlyToProgressReceiver; +import com.dji.sample.control.model.dto.ResultNotifyDTO; +import com.dji.sample.control.model.dto.TakeoffProgressReceiver; +import com.dji.sample.control.model.enums.DroneAuthorityEnum; +import com.dji.sample.control.model.enums.DroneControlMethodEnum; +import com.dji.sample.control.model.enums.RemoteDebugMethodEnum; +import com.dji.sample.control.model.param.*; import com.dji.sample.control.service.IControlService; import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.enums.DeviceModeCodeEnum; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; import com.dji.sample.manage.model.enums.UserTypeEnum; -import com.dji.sample.manage.model.receiver.BasicDeviceProperty; +import com.dji.sample.manage.service.IDevicePayloadService; +import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.manage.service.IDeviceService; +import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum; 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 java.util.Objects; +import java.util.Optional; import java.util.UUID; /** @@ -44,55 +52,49 @@ public class ControlServiceImpl implements IControlService { private ISendMessageService webSocketMessageService; @Autowired - private IWebSocketManageService webSocketManageService; + private IDeviceService deviceService; @Autowired - private IDeviceService deviceService; + private IDeviceRedisService deviceRedisService; @Autowired private ObjectMapper mapper; + @Autowired + private IDevicePayloadService devicePayloadService; + + private RemoteDebugHandler checkDebugCondition(String sn, RemoteDebugParam param, RemoteDebugMethodEnum controlMethodEnum) { + RemoteDebugHandler handler = Objects.nonNull(controlMethodEnum.getClazz()) ? + mapper.convertValue(Objects.nonNull(param) ? param : new Object(), controlMethodEnum.getClazz()) + : new RemoteDebugHandler(); + if (!handler.canPublish(sn)) { + throw new RuntimeException("The current state of the dock does not support this function."); + } + if (Objects.nonNull(param) && !handler.valid()) { + throw new RuntimeException(CommonErrorEnum.ILLEGAL_ARGUMENT.getErrorMsg()); + } + return handler; + } + @Override - public ResponseResult controlDock(String sn, String serviceIdentifier, RemoteDebugParam param) { - RemoteControlMethodEnum controlMethodEnum = RemoteControlMethodEnum.find(serviceIdentifier); - if (RemoteControlMethodEnum.UNKNOWN == controlMethodEnum) { + public ResponseResult controlDockDebug(String sn, String serviceIdentifier, RemoteDebugParam param) { + RemoteDebugMethodEnum controlMethodEnum = RemoteDebugMethodEnum.find(serviceIdentifier); + if (RemoteDebugMethodEnum.UNKNOWN == controlMethodEnum) { return ResponseResult.error("The " + serviceIdentifier + " method does not exist."); } - Object data = ""; - // 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); - } - data = basicDeviceProperty; - } + RemoteDebugHandler data = checkDebugCondition(sn, param, controlMethodEnum); - boolean isExist = deviceService.checkDeviceOnline(sn); + boolean isExist = deviceRedisService.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(); - ServiceReply serviceReplyOpt = messageSenderService.publishWithReply( - topic, CommonTopicResponse.builder() - .tid(UUID.randomUUID().toString()) - .bid(bid) - .method(serviceIdentifier) - .timestamp(System.currentTimeMillis()) - .data(data) - .build()); + ServiceReply serviceReply = messageSenderService.publishServicesTopic(sn, serviceIdentifier, data, bid); - ServiceReply serviceReply = mapper.convertValue( - serviceReplyOpt, new TypeReference>() {}); if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { return ResponseResult.error(serviceReply.getResult(), - Objects.nonNull(serviceReply.getOutput()) ? serviceReply.getOutput().getStatus() - : "error: " + serviceIdentifier + serviceReply.getResult()); + "error: " + serviceIdentifier + serviceReply.getResult()); } if (controlMethodEnum.getProgress()) { RedisOpsUtils.setWithExpire(serviceIdentifier + RedisConst.DELIMITER + bid, sn, @@ -101,17 +103,22 @@ public class ControlServiceImpl implements IControlService { return ResponseResult.success(); } - @Override - @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS, outputChannel = ChannelName.OUTBOUND) - public void handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers) { + /** + * Handles multi-state command progress information. + * @param receiver + * @param headers + * @return + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_CONTROL_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleControlProgress(CommonTopicReceiver receiver, MessageHeaders headers) { String key = receiver.getMethod() + RedisConst.DELIMITER + receiver.getBid(); if (RedisOpsUtils.getExpire(key) <= 0) { - return; + return receiver; } String sn = RedisOpsUtils.get(key).toString(); - EventsReceiver eventsReceiver = mapper.convertValue(receiver.getData(), - new TypeReference>(){}); + EventsReceiver eventsReceiver = mapper.convertValue(receiver.getData(), + new TypeReference>(){}); eventsReceiver.setBid(receiver.getBid()); eventsReceiver.setSn(sn); @@ -127,26 +134,168 @@ public class ControlServiceImpl implements IControlService { RedisOpsUtils.del(key); } - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn); - webSocketMessageService.sendBatch( - webSocketManageService.getValueWithWorkspaceAndUserType( - device.getWorkspaceId(), UserTypeEnum.WEB.getVal()), - CustomWebSocketMessage.builder() - .data(eventsReceiver) - .timestamp(System.currentTimeMillis()) - .bizCode(receiver.getMethod()) + Optional deviceOpt = deviceRedisService.getDeviceOnline(sn); + + if (deviceOpt.isEmpty()) { + throw new RuntimeException("The device is offline."); + } + + DeviceDTO device = deviceOpt.get(); + webSocketMessageService.sendBatch(device.getWorkspaceId(), UserTypeEnum.WEB.getVal(), + receiver.getMethod(), eventsReceiver); + + return receiver; + } + + private void checkFlyToCondition(String dockSn) { + // TODO 设备固件版本不兼容情况 + Optional dockOpt = deviceRedisService.getDeviceOnline(dockSn); + if (dockOpt.isEmpty()) { + throw new RuntimeException("The dock is offline, please restart the dock."); + } + + DeviceModeCodeEnum deviceMode = deviceService.getDeviceMode(dockOpt.get().getChildDeviceSn()); + if (DeviceModeCodeEnum.MANUAL != deviceMode) { + throw new RuntimeException("The current state of the drone does not support this function, please try again later."); + } + + ResponseResult result = seizeAuthority(dockSn, DroneAuthorityEnum.FLIGHT, null); + if (ResponseResult.CODE_SUCCESS != result.getCode()) { + throw new IllegalArgumentException(result.getMessage()); + } + } + + @Override + public ResponseResult flyToPoint(String sn, FlyToPointParam param) { + checkFlyToCondition(sn); + + param.setFlyToId(UUID.randomUUID().toString()); + ServiceReply reply = messageSenderService.publishServicesTopic(sn, DroneControlMethodEnum.FLY_TO_POINT.getMethod(), param, param.getFlyToId()); + return ResponseResult.CODE_SUCCESS != reply.getResult() ? + ResponseResult.error("Flying to the target point failed." + reply.getResult()) + : ResponseResult.success(); + } + + @Override + public ResponseResult flyToPointStop(String sn) { + ServiceReply reply = messageSenderService.publishServicesTopic(sn, DroneControlMethodEnum.FLY_TO_POINT_STOP.getMethod(), null); + return ResponseResult.CODE_SUCCESS != reply.getResult() ? + ResponseResult.error("The drone flying to the target point failed to stop. " + reply.getResult()) + : ResponseResult.success(); + } + + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLY_TO_POINT_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleFlyToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers) { + String dockSn = receiver.getGateway(); + + Optional deviceOpt = deviceRedisService.getDeviceOnline(dockSn); + if (deviceOpt.isEmpty()) { + log.error("The dock is offline."); + return null; + } + + FlyToProgressReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference(){}); + webSocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.FLY_TO_POINT_PROGRESS.getCode(), + ResultNotifyDTO.builder().sn(dockSn) + .message(WaylineErrorCodeEnum.SUCCESS == eventsReceiver.getResult() ? + eventsReceiver.getStatus().getMessage() : eventsReceiver.getResult().getErrorMsg()) + .result(eventsReceiver.getResult().getErrorCode()) + .build()); + return receiver; + } + + private void checkTakeoffCondition(String dockSn) { + Optional dockOpt = deviceRedisService.getDeviceOnline(dockSn); + if (dockOpt.isEmpty() || DockModeCodeEnum.IDLE != deviceService.getDockMode(dockSn)) { + throw new RuntimeException("The current state does not support takeoff."); + } + + ResponseResult result = seizeAuthority(dockSn, DroneAuthorityEnum.FLIGHT, null); + if (ResponseResult.CODE_SUCCESS != result.getCode()) { + throw new IllegalArgumentException(result.getMessage()); + } + + } + + @Override + public ResponseResult takeoffToPoint(String sn, TakeoffToPointParam param) { + checkTakeoffCondition(sn); + + param.setFlightId(UUID.randomUUID().toString()); + ServiceReply reply = messageSenderService.publishServicesTopic(sn, DroneControlMethodEnum.TAKE_OFF_TO_POINT.getMethod(), param, param.getFlightId()); + return ResponseResult.CODE_SUCCESS != reply.getResult() ? + ResponseResult.error("The drone failed to take off. " + reply.getResult()) + : ResponseResult.success(); + } + + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_TAKE_OFF_TO_POINT_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleTakeoffToPointProgress(CommonTopicReceiver receiver, MessageHeaders headers) { + String dockSn = receiver.getGateway(); + + Optional deviceOpt = deviceRedisService.getDeviceOnline(dockSn); + if (deviceOpt.isEmpty()) { + log.error("The dock is offline."); + return null; + } + TakeoffProgressReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference(){}); + + webSocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.TAKE_OFF_TO_POINT_PROGRESS.getCode(), + ResultNotifyDTO.builder().sn(dockSn) + .message(WaylineErrorCodeEnum.SUCCESS == eventsReceiver.getResult() ? + eventsReceiver.getStatus().getMessage() : eventsReceiver.getResult().getErrorMsg()) + .result(eventsReceiver.getResult().getErrorCode()) .build()); - if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) { - String topic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF; - messageSenderService.publish(topic, - CommonTopicResponse.builder() - .tid(receiver.getTid()) - .bid(receiver.getBid()) - .method(receiver.getMethod()) - .timestamp(System.currentTimeMillis()) - .data(RequestsReply.success()) - .build()); + return receiver; + } + + @Override + public ResponseResult seizeAuthority(String sn, DroneAuthorityEnum authority, DronePayloadParam param) { + String method; + switch (authority) { + case FLIGHT: + if (deviceService.checkAuthorityFlight(sn)) { + return ResponseResult.success(); + } + method = DroneControlMethodEnum.FLIGHT_AUTHORITY_GRAB.getMethod(); + break; + case PAYLOAD: + if (checkPayloadAuthority(sn, param.getPayloadIndex())) { + return ResponseResult.success(); + } + method = DroneControlMethodEnum.PAYLOAD_AUTHORITY_GRAB.getMethod(); + break; + default: + return ResponseResult.error(CommonErrorEnum.ILLEGAL_ARGUMENT); + } + ServiceReply serviceReply = messageSenderService.publishServicesTopic(sn, method, param); + return ResponseResult.CODE_SUCCESS != serviceReply.getResult() ? + ResponseResult.error(serviceReply.getResult(), "Method: " + method + " Error Code:" + serviceReply.getResult()) + : ResponseResult.success(); + } + + private Boolean checkPayloadAuthority(String sn, String payloadIndex) { + Optional dockOpt = deviceRedisService.getDeviceOnline(sn); + if (dockOpt.isEmpty()) { + throw new RuntimeException("The dock is offline, please restart the dock."); } + return devicePayloadService.checkAuthorityPayload(dockOpt.get().getChildDeviceSn(), payloadIndex); + } + + + @Override + public ResponseResult payloadCommands(PayloadCommandsParam param) throws Exception { + param.getCmd().getClazz() + .getDeclaredConstructor(DronePayloadParam.class) + .newInstance(param.getData()) + .checkCondition(param.getSn()); + + ServiceReply serviceReply = messageSenderService.publishServicesTopic(param.getSn(), param.getCmd().getCmd(), param.getData()); + return ResponseResult.CODE_SUCCESS != serviceReply.getResult() ? + ResponseResult.error(serviceReply.getResult(), " Error Code:" + serviceReply.getResult()) + : ResponseResult.success(); } + } diff --git a/src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java b/src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java new file mode 100644 index 0000000..7e089cc --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/DrcServiceImpl.java @@ -0,0 +1,249 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.common.model.ResponseResult; +import com.dji.sample.component.mqtt.config.MqttConfiguration; +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.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.dji.sample.control.model.dto.*; +import com.dji.sample.control.model.enums.DrcMethodEnum; +import com.dji.sample.control.model.enums.DrcStatusErrorEnum; +import com.dji.sample.control.model.enums.DroneAuthorityEnum; +import com.dji.sample.control.model.enums.MqttAclAccessEnum; +import com.dji.sample.control.model.param.DrcConnectParam; +import com.dji.sample.control.model.param.DrcModeParam; +import com.dji.sample.control.service.IControlService; +import com.dji.sample.control.service.IDrcService; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; +import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver; +import com.dji.sample.manage.service.IDeviceRedisService; +import com.dji.sample.manage.service.IDeviceService; +import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver; +import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum; +import com.dji.sample.wayline.model.enums.WaylineTaskStatusEnum; +import com.dji.sample.wayline.model.param.UpdateJobParam; +import com.dji.sample.wayline.service.IWaylineJobService; +import com.dji.sample.wayline.service.IWaylineRedisService; +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.messaging.MessageHeaders; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * @author sean + * @version 1.3 + * @date 2023/1/11 + */ +@Service +@Slf4j +public class DrcServiceImpl implements IDrcService { + + @Autowired + private IMessageSenderService messageSenderService; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private IWaylineJobService waylineJobService; + + @Autowired + private IDeviceService deviceService; + + @Autowired + private ObjectMapper mapper; + + @Autowired + private ISendMessageService webSocketMessageService; + + @Autowired + private IControlService controlService; + + @Autowired + private IDeviceRedisService deviceRedisService; + + @Autowired + private IWaylineRedisService waylineRedisService; + + @Override + public void setDrcModeInRedis(String dockSn, String clientId) { + RedisOpsUtils.setWithExpire(RedisConst.DRC_PREFIX + dockSn, clientId, RedisConst.DRC_MODE_ALIVE_SECOND); + } + + @Override + public String getDrcModeInRedis(String dockSn) { + return (String) RedisOpsUtils.get(RedisConst.DRC_PREFIX + dockSn); + } + + @Override + public Boolean delDrcModeInRedis(String dockSn) { + return RedisOpsUtils.del(RedisConst.DRC_PREFIX + dockSn); + } + + @Override + public MqttBrokerDTO userDrcAuth(String workspaceId, String userId, String username, DrcConnectParam param) { + + // refresh token + String clientId = param.getClientId(); + // first time + if (!StringUtils.hasText(clientId) || !RedisOpsUtils.checkExist(RedisConst.MQTT_ACL_PREFIX + clientId)) { + clientId = userId + "-" + System.currentTimeMillis(); + RedisOpsUtils.hashSet(RedisConst.MQTT_ACL_PREFIX + clientId, "", MqttAclAccessEnum.ALL.getValue()); + } + + String key = RedisConst.MQTT_ACL_PREFIX + clientId; + + try { + RedisOpsUtils.expireKey(key, RedisConst.DRC_MODE_ALIVE_SECOND); + + return MqttConfiguration.getMqttBrokerWithDrc( + clientId, username, param.getExpireSec(), Collections.emptyMap()); + } catch (RuntimeException e) { + RedisOpsUtils.del(key); + throw e; + } + } + + private void checkDrcModeCondition(String workspaceId, String dockSn) { + Optional> runningOpt = waylineRedisService.getRunningWaylineJob(dockSn); + if (runningOpt.isPresent() && WaylineJobStatusEnum.IN_PROGRESS == waylineJobService.getWaylineState(dockSn)) { + waylineJobService.updateJobStatus(workspaceId, runningOpt.get().getBid(), + UpdateJobParam.builder().status(WaylineTaskStatusEnum.PAUSE).build()); + } + + DockModeCodeEnum dockMode = deviceService.getDockMode(dockSn); + Optional dockOpt = deviceRedisService.getDeviceOnline(dockSn); + if (dockOpt.isPresent() && (DockModeCodeEnum.IDLE == dockMode || DockModeCodeEnum.WORKING == dockMode)) { + Optional deviceOsd = deviceRedisService.getDeviceOsd(dockOpt.get().getChildDeviceSn(), OsdSubDeviceReceiver.class); + if (deviceOsd.isEmpty() || deviceOsd.get().getElevation() <= 0) { + throw new RuntimeException("The drone is not in the sky and cannot enter command flight mode."); + } + } else { + throw new RuntimeException("The current state of the dock does not support entering command flight mode."); + } + + ResponseResult result = controlService.seizeAuthority(dockSn, DroneAuthorityEnum.FLIGHT, null); + if (ResponseResult.CODE_SUCCESS != result.getCode()) { + throw new IllegalArgumentException(result.getMessage()); + } + + } + + @Override + public JwtAclDTO deviceDrcEnter(String workspaceId, DrcModeParam param) { + String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + param.getDockSn() + TopicConst.DRC; + String pubTopic = topic + TopicConst.DOWN; + String subTopic = topic + TopicConst.UP; + + // If the dock is in drc mode, refresh the permissions directly. + if (deviceService.checkDockDrcMode(param.getDockSn()) + && param.getClientId().equals(this.getDrcModeInRedis(param.getDockSn()))) { + refreshAcl(param.getDockSn(), param.getClientId(), topic, subTopic); + return JwtAclDTO.builder().sub(List.of(subTopic)).pub(List.of(pubTopic)).build(); + } + + checkDrcModeCondition(workspaceId, param.getDockSn()); + + ServiceReply reply = messageSenderService.publishServicesTopic( + param.getDockSn(), DrcMethodEnum.DRC_MODE_ENTER.getMethod(), + DrcModeDTO.builder() + .mqttBroker(MqttConfiguration.getMqttBrokerWithDrc(param.getDockSn() + "-" + System.currentTimeMillis(), param.getDockSn(), + RedisConst.DRC_MODE_ALIVE_SECOND.longValue(), + Map.of(MapKeyConst.ACL, objectMapper.convertValue(JwtAclDTO.builder() + .pub(List.of(subTopic)) + .sub(List.of(pubTopic)) + .build(), new TypeReference>() {})))) + .build()); + + if (ResponseResult.CODE_SUCCESS != reply.getResult()) { + throw new RuntimeException("SN: " + param.getDockSn() + "; Error Code:" + reply.getResult() + "; Failed to enter command flight control mode, please try again later!"); + } + + refreshAcl(param.getDockSn(), param.getClientId(), pubTopic, subTopic); + return JwtAclDTO.builder().sub(List.of(subTopic)).pub(List.of(pubTopic)).build(); + } + + private void refreshAcl(String dockSn, String clientId, String pubTopic, String subTopic) { + this.setDrcModeInRedis(dockSn, clientId); + + // assign acl,Match by clientId. https://www.emqx.io/docs/zh/v4.4/advanced/acl-redis.html + // scheme: HSET mqtt_acl:[clientid] [topic] [access] + String key = RedisConst.MQTT_ACL_PREFIX + clientId; + RedisOpsUtils.hashSet(key, pubTopic, MqttAclAccessEnum.PUB.getValue()); + RedisOpsUtils.hashSet(key, subTopic, MqttAclAccessEnum.SUB.getValue()); + } + + @Override + public void deviceDrcExit(String workspaceId, DrcModeParam param) { + if (!deviceService.checkDockDrcMode(param.getDockSn())) { + throw new RuntimeException("The dock is not in flight control mode."); + } + ServiceReply reply = messageSenderService.publishServicesTopic( + param.getDockSn(), DrcMethodEnum.DRC_MODE_EXIT.getMethod(), ""); + if (ResponseResult.CODE_SUCCESS != reply.getResult()) { + throw new RuntimeException("SN: " + param.getDockSn() + "; Error Code:" + + reply.getResult() + "; Failed to exit command flight control mode, please try again later!"); + } + + String jobId = waylineRedisService.getPausedWaylineJobId(param.getDockSn()); + if (StringUtils.hasText(jobId)) { + waylineJobService.updateJobStatus(workspaceId, jobId, UpdateJobParam.builder().status(WaylineTaskStatusEnum.RESUME).build()); + } + + this.delDrcModeInRedis(param.getDockSn()); + RedisOpsUtils.del(RedisConst.MQTT_ACL_PREFIX + param.getClientId()); + } + + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_DRC_STATUS_NOTIFY, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleDrcStatusNotify(CommonTopicReceiver receiver, MessageHeaders headers) { + String dockSn = receiver.getGateway(); + + Optional deviceOpt = deviceRedisService.getDeviceOnline(dockSn); + if (deviceOpt.isEmpty()) { + return null; + } + + DrcStatusNotifyReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference(){}); + if (DrcStatusErrorEnum.SUCCESS != eventsReceiver.getResult()) { + webSocketMessageService.sendBatch( + deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), BizCodeEnum.DRC_STATUS_NOTIFY.getCode(), + ResultNotifyDTO.builder().sn(dockSn) + .message(eventsReceiver.getResult().getErrorMsg()) + .result(eventsReceiver.getResult().getErrorCode()).build()); + } + return receiver; + } + + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_DRC_MODE_EXIT_NOTIFY, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleDrcModeExitNotify(CommonTopicReceiver receiver, MessageHeaders headers) { + String dockSn = receiver.getGateway(); + + Optional deviceOpt = deviceRedisService.getDeviceOnline(dockSn); + if (deviceOpt.isEmpty()) { + return null; + } + + DrcModeReasonReceiver eventsReceiver = mapper.convertValue(receiver.getData(), new TypeReference(){}); + webSocketMessageService.sendBatch( + deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), BizCodeEnum.JOYSTICK_INVALID_NOTIFY.getCode(), + ResultNotifyDTO.builder().sn(dockSn) + .message(eventsReceiver.getReason().getMessage()) + .result(eventsReceiver.getReason().getVal()).build()); + return receiver; + } + +} diff --git a/src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java b/src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java new file mode 100644 index 0000000..4e7a310 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/GimbalResetImpl.java @@ -0,0 +1,22 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.control.model.param.DronePayloadParam; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public class GimbalResetImpl extends PayloadCommandsHandler { + public GimbalResetImpl(DronePayloadParam param) { + super(param); + } + + @Override + public boolean valid() { + return Objects.nonNull(param.getResetMode()); + } + +} diff --git a/src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java b/src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java new file mode 100644 index 0000000..c0eeee6 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/PayloadCommandsHandler.java @@ -0,0 +1,82 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.common.util.SpringBeanUtils; +import com.dji.sample.control.model.param.DronePayloadParam; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.receiver.OsdCameraReceiver; +import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver; +import com.dji.sample.manage.service.IDevicePayloadService; +import com.dji.sample.manage.service.IDeviceRedisService; + +import java.util.Optional; + +/** + * @author sean + * @version 1.4 + * @date 2023/4/23 + */ +public abstract class PayloadCommandsHandler { + + DronePayloadParam param; + + OsdCameraReceiver osdCamera; + + PayloadCommandsHandler(DronePayloadParam param) { + this.param = param; + } + + public boolean valid() { + return true; + } + + public boolean canPublish(String deviceSn) { + Optional deviceOpt = SpringBeanUtils.getBean(IDeviceRedisService.class) + .getDeviceOsd(deviceSn, OsdSubDeviceReceiver.class); + if (deviceOpt.isEmpty()) { + throw new RuntimeException("The device is offline."); + } + osdCamera = deviceOpt.get().getCameras().stream() + .filter(osdCamera -> param.getPayloadIndex().equals(osdCamera.getPayloadIndex())) + .findAny() + .orElseThrow(() -> new RuntimeException("Did not receive osd information about the camera, please check the cache data.")); + return true; + } + + private String checkDockOnline(String dockSn) { + Optional deviceOpt = SpringBeanUtils.getBean(IDeviceRedisService.class).getDeviceOnline(dockSn); + if (deviceOpt.isEmpty()) { + throw new RuntimeException("The dock is offline."); + } + return deviceOpt.get().getChildDeviceSn(); + } + + private void checkDeviceOnline(String deviceSn) { + boolean isOnline = SpringBeanUtils.getBean(IDeviceRedisService.class).checkDeviceOnline(deviceSn); + if (!isOnline) { + throw new RuntimeException("The device is offline."); + } + } + + private void checkAuthority(String deviceSn) { + boolean hasAuthority = SpringBeanUtils.getBean(IDevicePayloadService.class) + .checkAuthorityPayload(deviceSn, param.getPayloadIndex()); + if (!hasAuthority) { + throw new RuntimeException("The device does not have payload control authority."); + } + } + + public final void checkCondition(String dockSn) { + if (!valid()) { + throw new RuntimeException("illegal argument"); + } + + String deviceSn = checkDockOnline(dockSn); + checkDeviceOnline(deviceSn); + checkAuthority(deviceSn); + + if (!canPublish(deviceSn)) { + throw new RuntimeException("The current state of the drone does not support this function, please try again later."); + } + } + +} diff --git a/src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java b/src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java new file mode 100644 index 0000000..ed33e51 --- /dev/null +++ b/src/main/java/com/dji/sample/control/service/impl/RemoteDebugHandler.java @@ -0,0 +1,23 @@ +package com.dji.sample.control.service.impl; + +import com.dji.sample.common.util.SpringBeanUtils; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; +import com.dji.sample.manage.service.IDeviceService; + +/** + * @author sean + * @version 1.3 + * @date 2022/10/27 + */ +public class RemoteDebugHandler { + + public boolean valid() { + return false; + } + + public boolean canPublish(String sn) { + IDeviceService deviceService = SpringBeanUtils.getBean(IDeviceService.class); + DockModeCodeEnum dockMode = deviceService.getDockMode(sn); + return DockModeCodeEnum.REMOTE_DEBUGGING == dockMode; + } +} diff --git a/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java b/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java deleted file mode 100644 index 17a7658..0000000 --- a/src/main/java/com/dji/sample/manage/controller/DevicePayloadController.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.dji.sample.manage.controller; - -import com.dji.sample.component.mqtt.model.ChannelName; -import com.dji.sample.manage.model.receiver.DeviceBasicReceiver; -import com.dji.sample.manage.service.IDevicePayloadService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.messaging.MessageHeaders; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author sean.zhou - * @date 2021/11/19 - * @version 0.1 - */ -@RestController -public class DevicePayloadController { - - @Autowired - private IDevicePayloadService devicePayloadService; - - /** - * Handles messages in the state topic about basic drone data. - * - * Note: Only the data of the drone payload is handled here. You can handle other data from the drone - * according to your business needs. - * @param deviceBasic basic drone data - * @param headers - */ - @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC) - public void stateBasic(DeviceBasicReceiver deviceBasic, MessageHeaders headers) { - - devicePayloadService.saveDeviceBasicPayload(deviceBasic.getPayloads(), headers.getTimestamp()); - } -} \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java new file mode 100644 index 0000000..72621e1 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceAuthorityDTO.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.dto; + +import com.dji.sample.control.model.enums.DroneAuthorityEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/2 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DeviceAuthorityDTO { + + private String sn; + + private DroneAuthorityEnum type; + + private String controlSource; + +} diff --git a/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java index 68466d7..d4c7399 100644 --- a/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java +++ b/src/main/java/com/dji/sample/manage/model/dto/DeviceDTO.java @@ -25,7 +25,7 @@ public class DeviceDTO { private String workspaceId; - private String deviceIndex; + private String controlSource; private String deviceDesc; @@ -62,4 +62,6 @@ public class DeviceDTO { private Integer firmwareStatus; private Integer firmwareProgress; + + private String parentSn; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java index bb16641..555eb90 100644 --- a/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java +++ b/src/main/java/com/dji/sample/manage/model/dto/DevicePayloadDTO.java @@ -23,7 +23,11 @@ public class DevicePayloadDTO { private String payloadName; - private Integer payloadIndex; + private Integer index; private String payloadDesc; + + private String controlSource; + + private String payloadIndex; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java index a54ff0d..c50db1e 100644 --- a/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java +++ b/src/main/java/com/dji/sample/manage/model/entity/DevicePayloadEntity.java @@ -53,4 +53,7 @@ public class DevicePayloadEntity implements Serializable { @TableField(value = "payload_desc") private String payloadDesc; + @TableField(value = "control_source") + private String controlSource; + } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java b/src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java new file mode 100644 index 0000000..764bc3c --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/ControlSourceEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/16 + */ +public enum ControlSourceEnum { + + A, B; + + @JsonValue + public String getControlSource() { + return name(); + } + + @JsonCreator + public static ControlSourceEnum find(String controlSource) { + return Arrays.stream(values()).filter(controlSourceEnum -> controlSourceEnum.name().equals(controlSource)).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java index 22475b2..2ce85a2 100644 --- a/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java +++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceDomainEnum.java @@ -2,6 +2,8 @@ package com.dji.sample.manage.model.enums; import lombok.Getter; +import java.util.Arrays; + /** * * @author sean.zhou @@ -17,11 +19,17 @@ public enum DeviceDomainEnum { PAYLOAD(1), - DOCK (3); + DOCK (3), + + UNKNOWN(-1); int val; DeviceDomainEnum(int val) { this.val = val; } + + public static DeviceDomainEnum find(int val) { + return Arrays.stream(values()).filter(domainEnum -> domainEnum.val == val).findAny().orElse(UNKNOWN); + } } diff --git a/src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java new file mode 100644 index 0000000..9957032 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceModeCodeEnum.java @@ -0,0 +1,64 @@ +package com.dji.sample.manage.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/9 + */ +public enum DeviceModeCodeEnum { + + IDLE(0), + + TAKEOFF_PREPARE(1), + + TAKEOFF_FINISHED(2), + + MANUAL(3), + + TAKEOFF_AUTO(4), + + WAYLINE(5), + + PANORAMIC_SHOT(6), + + ACTIVE_TRACK(7), + + ADS_B_AVOIDANCE(8), + + RETURN_AUTO(9), + + LANDING_AUTO(10), + + LANDING_FORCED(11), + + LANDING_THREE_PROPELLER(12), + + UPGRADING(13), + + DISCONNECTED(14), + + APAS(15), + + VIRTUAL_JOYSTICK(16); + + int val; + + DeviceModeCodeEnum(int val) { + this.val = val; + } + + @JsonValue + public int getVal() { + return val; + } + + @JsonCreator + public static DeviceModeCodeEnum find(int value) { + return Arrays.stream(values()).filter(modeCodeEnum -> modeCodeEnum.ordinal() == value).findAny().orElse(DISCONNECTED); + } +} 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 index 214767c..07c6658 100644 --- a/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java +++ b/src/main/java/com/dji/sample/manage/model/enums/DeviceSetPropertyEnum.java @@ -20,8 +20,11 @@ public enum DeviceSetPropertyEnum { DISTANCE_LIMIT_STATUS("distance_limit_status", DistanceLimitStatusReceiver.class), - OBSTACLE_AVOIDANCE("obstacle_avoidance", ObstacleAvoidanceReceiver.class); + OBSTACLE_AVOIDANCE("obstacle_avoidance", ObstacleAvoidanceReceiver.class), + RTH_ALTITUDE("rth_altitude", RthAltitudeReceiver.class), + + OUT_OF_CONTROL_ACTION("out_of_control_action", OutOfControlActionReceiver.class); String property; diff --git a/src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java new file mode 100644 index 0000000..9683f1f --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/DockDrcStateEnum.java @@ -0,0 +1,36 @@ +package com.dji.sample.manage.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/2/28 + */ +public enum DockDrcStateEnum { + + DISCONNECTED(0), + + CONNECTING(1), + + CONNECTED(2); + + int val; + + DockDrcStateEnum(int val) { + this.val = val; + } + + @JsonValue + public int getVal() { + return val; + } + + @JsonCreator + public static DockDrcStateEnum find(int val) { + return Arrays.stream(values()).filter(drcState -> drcState.getVal() == val).findAny().orElse(DISCONNECTED); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java new file mode 100644 index 0000000..ae1aa91 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/DockModeCodeEnum.java @@ -0,0 +1,42 @@ +package com.dji.sample.manage.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/2/28 + */ +public enum DockModeCodeEnum { + + IDLE(0), + + DEBUGGING(1), + + REMOTE_DEBUGGING(2), + + UPGRADING(3), + + WORKING(4), + + DISCONNECTED(-1); + + int val; + + DockModeCodeEnum(int val) { + this.val = val; + } + + @JsonValue + public int getVal() { + return val; + } + + @JsonCreator + public static DockModeCodeEnum find(int val) { + return Arrays.stream(values()).filter(modeCode -> modeCode.getVal() == val).findAny().orElse(DISCONNECTED); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java b/src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java new file mode 100644 index 0000000..116847e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/DroneRcLostActionEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/1 + */ +public enum DroneRcLostActionEnum { + + HOVER, LAND, RETURN_HOME; + + @JsonValue + public int getVal() { + return ordinal(); + } + + @JsonCreator + public static DroneRcLostActionEnum find(int val) { + return Arrays.stream(values()).filter(controlActionEnum -> controlActionEnum.ordinal() == val).findAny().get(); + } +} diff --git a/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java b/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java index 04b083a..20af72f 100644 --- a/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java +++ b/src/main/java/com/dji/sample/manage/model/enums/HmsEnum.java @@ -37,20 +37,6 @@ public enum HmsEnum { } } - @Getter - public enum DomainType { - DRONE_NEST("drone_nest"), - - DRONE("drone"); - - private String domain; - - DomainType(String domain) { - this.domain = domain; - } - - } - @Getter public enum HmsFaqIdEnum { diff --git a/src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java b/src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java new file mode 100644 index 0000000..3632452 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/enums/WaylineRcLostActionEnum.java @@ -0,0 +1,26 @@ +package com.dji.sample.manage.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/15 + */ +public enum WaylineRcLostActionEnum { + + CONTINUE_WAYLINE, EXECUTE_RC_LOST_ACTION; + + @JsonValue + public int getVal() { + return ordinal(); + } + + @JsonCreator + public static WaylineRcLostActionEnum find(int val) { + return Arrays.stream(values()).filter(lostActionEnum -> lostActionEnum.ordinal() == val).findAny().get(); + } +} 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 index b232ce8..f32273d 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/BasicDeviceProperty.java @@ -1,14 +1,11 @@ package com.dji.sample.manage.model.receiver; -import lombok.Data; - /** * @author sean * @version 1.3 * @date 2022/10/27 */ -@Data -public class BasicDeviceProperty { +public abstract class BasicDeviceProperty { public boolean valid() { return false; diff --git a/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java index f2557a5..cc0d66c 100644 --- a/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java +++ b/src/main/java/com/dji/sample/manage/model/receiver/DeviceHmsReceiver.java @@ -14,8 +14,6 @@ public class DeviceHmsReceiver { private String deviceType; - private String domainType; - private Integer imminent; private Integer inTheSky; diff --git a/src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java new file mode 100644 index 0000000..9818b93 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/FirmwareProgressExtReceiver.java @@ -0,0 +1,14 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/30 + */ +@Data +public class FirmwareProgressExtReceiver { + + private Long rate; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java new file mode 100644 index 0000000..b6a09e9 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/LiveviewWorldRegionReceiver.java @@ -0,0 +1,20 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/8 + */ +@Data +public class LiveviewWorldRegionReceiver { + + private Double bottom; + + private Double left; + + private Double right; + + private Double top; +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java new file mode 100644 index 0000000..96de1bc --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/OsdCameraReceiver.java @@ -0,0 +1,34 @@ +package com.dji.sample.manage.model.receiver; + +import com.dji.sample.control.model.enums.CameraModeEnum; +import com.dji.sample.control.model.enums.CameraStateEnum; +import lombok.Data; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/8 + */ +@Data +public class OsdCameraReceiver { + + private CameraModeEnum cameraMode; + + private LiveviewWorldRegionReceiver liveviewWorldRegion; + + private String payloadIndex; + + private CameraStateEnum photoState; + + private Integer recordTime; + + private CameraStateEnum recordingState; + + private Long remainPhotoNum; + + private Long remainRecordDuration; + + private Float zoomFactor; + + private Float irZoomFactor; +} 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 0cd5196..8826d35 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 @@ -1,5 +1,7 @@ package com.dji.sample.manage.model.receiver; +import com.dji.sample.manage.model.enums.DockDrcStateEnum; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; import lombok.Data; /** @@ -40,13 +42,13 @@ public class OsdDockReceiver { private StorageReceiver storage; - private Integer modeCode; + private DockModeCodeEnum modeCode; private Integer coverState; private Integer supplementLightState; - private Integer emergencyStopState; + private Boolean emergencyStopState; private Integer airConditionerMode; @@ -85,4 +87,6 @@ public class OsdDockReceiver { private DockSdrReceiver sdr; private DockWirelessLinkReceiver wirelessLink; + + private DockDrcStateEnum drcState; } 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 b39fb09..148ad9a 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 @@ -1,5 +1,8 @@ package com.dji.sample.manage.model.receiver; +import com.dji.sample.manage.model.enums.DeviceModeCodeEnum; +import com.dji.sample.manage.model.enums.DroneRcLostActionEnum; +import com.dji.sample.manage.model.enums.WaylineRcLostActionEnum; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -41,7 +44,7 @@ public class OsdSubDeviceReceiver { private Double longitude; - private Integer modeCode; + private DeviceModeCodeEnum modeCode; private Double totalFlightDistance; @@ -67,4 +70,15 @@ public class OsdSubDeviceReceiver { private ObstacleAvoidanceReceiver obstacleAvoidance; + private Long activationTime; + + private List cameras; + + private DroneRcLostActionEnum rcLostAction; + + private Integer rthAltitude; + + private Integer totalFlightSorties; + + private WaylineRcLostActionEnum exitWaylineWhenRcLost; } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java new file mode 100644 index 0000000..836242e --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/OutOfControlActionReceiver.java @@ -0,0 +1,29 @@ +package com.dji.sample.manage.model.receiver; + +import com.dji.sample.manage.model.enums.DroneRcLostActionEnum; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/3 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OutOfControlActionReceiver extends BasicDeviceProperty { + + private Integer value; + + @Override + public boolean valid() { + return Objects.nonNull(value) && value >= 0 && value < DroneRcLostActionEnum.values().length; + } + +} diff --git a/src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java b/src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java new file mode 100644 index 0000000..1727944 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/model/receiver/RthAltitudeReceiver.java @@ -0,0 +1,32 @@ +package com.dji.sample.manage.model.receiver; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/3 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RthAltitudeReceiver extends BasicDeviceProperty { + + private Integer value; + + private static final int RTH_ALTITUDE_MAX = 500; + + private static final int RTH_ALTITUDE_MIN = 20; + + @Override + public boolean valid() { + return Objects.nonNull(this.value) && this.value >= RTH_ALTITUDE_MIN && this.value <= RTH_ALTITUDE_MAX; + } + +} diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java b/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java index 2d82e7b..72f50ab 100644 --- a/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java +++ b/src/main/java/com/dji/sample/manage/service/IDeviceFirmwareService.java @@ -1,14 +1,12 @@ package com.dji.sample.manage.service; import com.dji.sample.common.model.PaginationData; -import com.dji.sample.component.mqtt.model.CommonTopicReceiver; import com.dji.sample.manage.model.dto.DeviceFirmwareDTO; import com.dji.sample.manage.model.dto.DeviceFirmwareNoteDTO; import com.dji.sample.manage.model.dto.DeviceFirmwareUpgradeDTO; import com.dji.sample.manage.model.param.DeviceFirmwareQueryParam; import com.dji.sample.manage.model.param.DeviceFirmwareUploadParam; import com.dji.sample.manage.model.param.DeviceOtaCreateParam; -import org.springframework.messaging.MessageHeaders; import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -47,13 +45,6 @@ public interface IDeviceFirmwareService { */ List getDeviceOtaFirmware(String workspaceId, List upgradeDTOS); - /** - * Interface to handle device firmware update progress. - * @param receiver - * @param headers - */ - void handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers); - /** * Query firmware version information by page. * diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java b/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java index f115719..aded702 100644 --- a/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java +++ b/src/main/java/com/dji/sample/manage/service/IDeviceLogsService.java @@ -74,8 +74,9 @@ public interface IDeviceLogsService { * Handle logs file upload progress. * @param receiver * @param headers + * @return */ - void handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers); + CommonTopicReceiver handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers); /** * Update status, which is updated when the logs upload succeeds or fails. diff --git a/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java index 04296c3..11fd20c 100644 --- a/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java +++ b/src/main/java/com/dji/sample/manage/service/IDevicePayloadService.java @@ -54,16 +54,17 @@ public interface IDevicePayloadService { */ void updateFirmwareVersion(FirmwareVersionReceiver receiver); - /** - * Handle the topic that contains the payloads field in the state, and save the payloads data. - * @param payloadReceiverList - * @param timestamp - */ - void saveDeviceBasicPayload(List payloadReceiverList, Long timestamp); - /** * Delete payload data based on payload sn. * @param payloadSns */ void deletePayloadsByPayloadsSn(Collection payloadSns); + + /** + * Check if the device has payload control. + * @param deviceSn + * @param payloadIndex + * @return + */ + Boolean checkAuthorityPayload(String deviceSn, String payloadIndex); } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java b/src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java new file mode 100644 index 0000000..074101a --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/IDeviceRedisService.java @@ -0,0 +1,95 @@ +package com.dji.sample.manage.service; + +import com.dji.sample.component.mqtt.model.EventsOutputProgressReceiver; +import com.dji.sample.component.mqtt.model.EventsReceiver; +import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.receiver.FirmwareProgressExtReceiver; + +import java.util.Optional; +import java.util.Set; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/21 + */ +public interface IDeviceRedisService { + + /** + * Determine if the device is online. + * @param sn + * @return + */ + Boolean checkDeviceOnline(String sn); + + /** + * Query the basic information of the device in redis. + * @param sn + * @return + */ + Optional getDeviceOnline(String sn); + + /** + * Save the basic information of the device in redis. + * @param device + */ + void setDeviceOnline(DeviceDTO device); + + /** + * Delete the basic device information saved in redis. + * @param sn + * @return + */ + Boolean delDeviceOnline(String sn); + + /** + * Get the device's osd real-time data. + * @param sn + * @param clazz + * @param + * @return + */ + Optional getDeviceOsd(String sn, Class clazz); + + /** + * Save the firmware update progress of the device in redis. + * @param sn + * @param events + */ + void setFirmwareUpgrading(String sn, EventsReceiver> events); + + /** + * Query the firmware update progress of the device in redis. + * @param sn + * @return + */ + Optional>> getFirmwareUpgradingProgress(String sn); + + /** + * Delete the firmware update progress of the device in redis. + * @param sn + * @return + */ + Boolean delFirmwareUpgrading(String sn); + + /** + * Save the hms key of the device in redis. + * @param sn + * @param keys + */ + void addEndHmsKeys(String sn, String... keys); + + /** + * Query all hms keys of the device in redis. + * @param sn + * @return + */ + Set getAllHmsKeys(String sn); + + /** + * Delete all hms keys of the device in redis. + * @param sn + * @return + */ + Boolean delHmsKeysBySn(String sn); +} 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 11bb1d7..7cf1a17 100644 --- a/src/main/java/com/dji/sample/manage/service/IDeviceService.java +++ b/src/main/java/com/dji/sample/manage/service/IDeviceService.java @@ -2,19 +2,18 @@ package com.dji.sample.manage.service; 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.component.mqtt.model.CommonTopicResponse; 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.DeviceModeCodeEnum; import com.dji.sample.manage.model.enums.DeviceSetPropertyEnum; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; 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; @@ -154,24 +153,6 @@ public interface IDeviceService { */ Boolean bindDevice(DeviceDTO device); - /** - * Handle dock binding status requests. - * Note: If your business does not need to bind the dock to the organization, - * you can directly reply to the successful message without implementing business logic. - * @param receiver - * @param headers - */ - void bindStatus(CommonTopicReceiver receiver, MessageHeaders headers); - - /** - * Handle dock binding requests. - * Note: If your business does not need to bind the dock to the organization, - * you can directly reply to the successful message without implementing business logic. - * @param receiver - * @param headers - */ - void bindDevice(CommonTopicReceiver receiver, MessageHeaders headers); - /** * Get the binding devices list in one workspace. * @param workspaceId @@ -195,12 +176,6 @@ public interface IDeviceService { */ Optional getDeviceBySn(String sn); - /** - * Update the firmware version information of the device or payload. - * @param receiver - */ - void updateFirmwareVersion(FirmwareVersionReceiver receiver); - /** * Create job for device firmware updates. * @param workspaceId @@ -227,9 +202,31 @@ public interface IDeviceService { void deviceOnePropertySet(String topic, DeviceSetPropertyEnum propertyEnum, Map.Entry value); /** - * Determine if the device is online. - * @param sn + * Check the working status of the dock. + * @param dockSn * @return */ - Boolean checkDeviceOnline(String sn); + DockModeCodeEnum getDockMode(String dockSn); + + /** + * Query the working status of the aircraft. + * @param deviceSn + * @return + */ + DeviceModeCodeEnum getDeviceMode(String deviceSn); + + /** + * Check if the dock is in drc mode. + * @param dockSn + * @return + */ + Boolean checkDockDrcMode(String dockSn); + + /** + * Check if the device has flight control. + * @param gatewaySn + * @return + */ + Boolean checkAuthorityFlight(String gatewaySn); + } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java index 2d80777..306760d 100644 --- a/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java +++ b/src/main/java/com/dji/sample/manage/service/IWorkspaceService.java @@ -25,8 +25,6 @@ public interface IWorkspaceService { /** * Handle the request for obtaining the organization information corresponding to the device binding. - * Note: If your business does not need to bind the dock to the organization, - * you can directly reply to the successful message without implementing business logic. * @param receiver */ void replyOrganizationGet(CommonTopicReceiver receiver, MessageHeaders headers); 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 926fa36..ec8ac77 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 @@ -1,6 +1,5 @@ package com.dji.sample.manage.service.impl; -import com.dji.sample.component.mqtt.model.StateDataEnum; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.manage.model.dto.CapacityCameraDTO; @@ -36,20 +35,19 @@ public class CapacityCameraServiceImpl implements ICapacityCameraService { @Override public List getCapacityCameraByDeviceSn(String deviceSn) { - return (List) RedisOpsUtils.hashGet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn); + return (List) RedisOpsUtils.hashGet(RedisConst.LIVE_CAPACITY, deviceSn); } @Override public Boolean deleteCapacityCameraByDeviceSn(String deviceSn) { - return RedisOpsUtils.hashDel(StateDataEnum.LIVE_CAPACITY.getDesc(), new String[]{deviceSn}); + return RedisOpsUtils.hashDel(RedisConst.LIVE_CAPACITY, new String[]{deviceSn}); } @Override public void saveCapacityCameraReceiverList(List capacityCameraReceivers, String deviceSn, Long timestamp) { List capacity = capacityCameraReceivers.stream() .map(this::receiver2Dto).collect(Collectors.toList()); - RedisOpsUtils.hashSet(StateDataEnum.LIVE_CAPACITY.getDesc(), deviceSn, capacity); - RedisOpsUtils.setWithExpire(StateDataEnum.LIVE_CAPACITY + RedisConst.DELIMITER + deviceSn, timestamp, RedisConst.DEVICE_ALIVE_SECOND); + RedisOpsUtils.hashSet(RedisConst.LIVE_CAPACITY, deviceSn, capacity); } @Override 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 6b99d38..1747040 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 @@ -13,8 +13,7 @@ import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.service.impl.OssServiceContext; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; -import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; -import com.dji.sample.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.BizCodeEnum; import com.dji.sample.component.websocket.service.IWebSocketManageService; import com.dji.sample.component.websocket.service.impl.SendMessageServiceImpl; import com.dji.sample.manage.dao.IDeviceFirmwareMapper; @@ -24,15 +23,15 @@ import com.dji.sample.manage.model.enums.UserTypeEnum; import com.dji.sample.manage.model.param.DeviceFirmwareQueryParam; import com.dji.sample.manage.model.param.DeviceFirmwareUploadParam; import com.dji.sample.manage.model.param.DeviceOtaCreateParam; +import com.dji.sample.manage.model.receiver.FirmwareProgressExtReceiver; import com.dji.sample.manage.service.IDeviceFirmwareService; -import com.dji.sample.manage.service.IDeviceService; +import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.manage.service.IFirmwareModelService; 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.util.DigestUtils; @@ -76,15 +75,15 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { @Autowired private IWebSocketManageService webSocketManageService; - @Autowired - private IDeviceService deviceService; - @Autowired private OssServiceContext ossServiceContext; @Autowired private IFirmwareModelService firmwareModelService; + @Autowired + private IDeviceRedisService deviceRedisService; + @Override public Optional getFirmware(String workspaceId, String deviceName, String version) { return Optional.ofNullable(entity2Dto(mapper.selectOne( @@ -108,7 +107,7 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { public List getDeviceOtaFirmware(String workspaceId, List upgradeDTOS) { List deviceOtaList = new ArrayList<>(); upgradeDTOS.forEach(upgradeDevice -> { - boolean exist = deviceService.checkDeviceOnline(upgradeDevice.getSn()); + boolean exist = deviceRedisService.checkDeviceOnline(upgradeDevice.getSn()); if (!exist) { throw new IllegalArgumentException("Device is offline."); } @@ -125,18 +124,15 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { return deviceOtaList; } - @Override - @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_OTA_PROGRESS, outputChannel = ChannelName.OUTBOUND) - public void handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers) { - String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); - String sn = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(), - topic.indexOf(TopicConst.EVENTS_SUF)); - - EventsReceiver eventsReceiver = objectMapper.convertValue(receiver.getData(), - new TypeReference>(){}); + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_OTA_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleOtaProgress(CommonTopicReceiver receiver, MessageHeaders headers) { + String sn = receiver.getGateway(); + + EventsReceiver> eventsReceiver = objectMapper.convertValue(receiver.getData(), + new TypeReference>>(){}); eventsReceiver.setBid(receiver.getBid()); - EventsOutputReceiver output = eventsReceiver.getOutput(); + EventsOutputProgressReceiver output = eventsReceiver.getOutput(); log.info("SN: {}, {} ===> Upgrading progress: {}", sn, receiver.getMethod(), output.getProgress().toString()); @@ -144,59 +140,34 @@ public class DeviceFirmwareServiceImpl implements IDeviceFirmwareService { log.error("SN: {}, {} ===> Error code: {}", sn, receiver.getMethod(), eventsReceiver.getResult()); } - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn); - String childDeviceSn = device.getChildDeviceSn(); - boolean upgrade = RedisOpsUtils.getExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn) > 0; - boolean childUpgrade = RedisOpsUtils.getExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn) > 0; + Optional deviceOpt = deviceRedisService.getDeviceOnline(sn); + if (deviceOpt.isEmpty()) { + return null; + } - // Determine whether it is the ending state, delete the update state key in redis after the job ends. EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus()); - Collection sessions = webSocketManageService.getValueWithWorkspaceAndUserType( - device.getWorkspaceId(), UserTypeEnum.WEB.getVal()); - CustomWebSocketMessage build = CustomWebSocketMessage.builder() - .data(eventsReceiver) - .timestamp(System.currentTimeMillis()) - .bizCode(receiver.getMethod()) - .build(); - if (upgrade) { - if (statusEnum.getEnd()) { - // Delete the cache after the update is complete. - RedisOpsUtils.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn); - } else { - // Update the update progress of the dock in redis. - RedisOpsUtils.setWithExpire( - RedisConst.FIRMWARE_UPGRADING_PREFIX + sn, output.getProgress().getPercent(), - RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); - } - eventsReceiver.setSn(sn); - webSocketMessageService.sendBatch(sessions, build); - } - if (childUpgrade) { - if (!StringUtils.hasText(eventsReceiver.getSn())) { - eventsReceiver.setSn(childDeviceSn); - webSocketMessageService.sendBatch(sessions, build); - } - if (statusEnum.getEnd()) { - RedisOpsUtils.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn); - } else { - // Update the update progress of the drone in redis. - RedisOpsUtils.setWithExpire( - RedisConst.FIRMWARE_UPGRADING_PREFIX + childDeviceSn, output.getProgress().getPercent(), - RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); - } - } + DeviceDTO device = deviceOpt.get(); + handleProgress(device.getWorkspaceId(), sn, eventsReceiver, statusEnum.getEnd()); + handleProgress(device.getWorkspaceId(), device.getChildDeviceSn(), eventsReceiver, statusEnum.getEnd()); - if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) { - String replyTopic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF; - messageSenderService.publish(replyTopic, - CommonTopicResponse.builder() - .tid(receiver.getTid()) - .bid(receiver.getBid()) - .method(receiver.getMethod()) - .timestamp(System.currentTimeMillis()) - .data(RequestsReply.success()) - .build()); + return receiver; + } + + private void handleProgress(String workspaceId, String sn, + EventsReceiver> events, boolean isEnd) { + boolean upgrade = deviceRedisService.getFirmwareUpgradingProgress(sn).isPresent(); + if (!upgrade) { + return; + } + if (isEnd) { + // Delete the cache after the update is complete. + deviceRedisService.delFirmwareUpgrading(sn); + } else { + // Update the update progress of the dock in redis. + deviceRedisService.setFirmwareUpgrading(sn, events); } + events.setSn(sn); + webSocketMessageService.sendBatch(workspaceId, UserTypeEnum.WEB.getVal(), BizCodeEnum.OTA_PROGRESS.getCode(), events); } @Override diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java index de1d03f..e201155 100644 --- a/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceHmsServiceImpl.java @@ -9,11 +9,7 @@ import com.dji.sample.component.mqtt.model.ChannelName; import com.dji.sample.component.mqtt.model.CommonTopicReceiver; import com.dji.sample.component.mqtt.model.MapKeyConst; import com.dji.sample.component.mqtt.model.TopicConst; -import com.dji.sample.component.redis.RedisConst; -import com.dji.sample.component.redis.RedisOpsUtils; -import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; import com.dji.sample.component.websocket.model.BizCodeEnum; -import com.dji.sample.component.websocket.model.CustomWebSocketMessage; import com.dji.sample.component.websocket.service.impl.SendMessageServiceImpl; import com.dji.sample.component.websocket.service.impl.WebSocketManageServiceImpl; import com.dji.sample.manage.dao.IDeviceHmsMapper; @@ -23,14 +19,17 @@ import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.dto.DeviceHmsDTO; import com.dji.sample.manage.model.dto.TelemetryDTO; import com.dji.sample.manage.model.entity.DeviceHmsEntity; +import com.dji.sample.manage.model.enums.DeviceDomainEnum; import com.dji.sample.manage.model.enums.HmsEnum; import com.dji.sample.manage.model.enums.UserTypeEnum; import com.dji.sample.manage.model.param.DeviceHmsQueryParam; import com.dji.sample.manage.model.receiver.DeviceHmsReceiver; import com.dji.sample.manage.model.receiver.HmsArgsReceiver; import com.dji.sample.manage.service.IDeviceHmsService; +import com.dji.sample.manage.service.IDeviceRedisService; 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; @@ -54,6 +53,7 @@ import java.util.stream.Collectors; */ @Service @Transactional +@Slf4j public class DeviceHmsServiceImpl implements IDeviceHmsService { @Autowired @@ -68,6 +68,9 @@ public class DeviceHmsServiceImpl implements IDeviceHmsService { @Autowired private WebSocketManageServiceImpl webSocketManageService; + @Autowired + private IDeviceRedisService deviceRedisService; + private static final Pattern PATTERN_KEY = Pattern.compile( HmsEnum.FormatKeyEnum.KEY_START + "(" + @@ -90,11 +93,8 @@ public class DeviceHmsServiceImpl implements IDeviceHmsService { .updateTime(0L) .sn(sn) .build(); - String key = RedisConst.HMS_PREFIX + sn; // Query all unread hms messages of the device in redis. - Set hmsMap = RedisOpsUtils.listGetAll(key).stream().map(String::valueOf).collect(Collectors.toSet()); - - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn); + Set hmsMap = deviceRedisService.getAllHmsKeys(sn); List unReadList = new ArrayList<>(); objectMapper.convertValue(((Map) (receiver.getData())).get(MapKeyConst.LIST), @@ -114,15 +114,14 @@ public class DeviceHmsServiceImpl implements IDeviceHmsService { if (unReadList.isEmpty()) { return; } - RedisOpsUtils.listRPush(key, unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new)); + deviceRedisService.addEndHmsKeys(sn, unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new)); // push to the web - Collection sessions = webSocketManageService.getValueWithWorkspaceAndUserType( - device.getWorkspaceId(), UserTypeEnum.WEB.getVal()); - sendMessageService.sendBatch(sessions, CustomWebSocketMessage.builder() - .bizCode(BizCodeEnum.DEVICE_HMS.getCode()) - .data(TelemetryDTO.>builder().sn(sn).host(unReadList).build()) - .timestamp(System.currentTimeMillis()) - .build()); + Optional deviceOpt = deviceRedisService.getDeviceOnline(sn); + if (deviceOpt.isEmpty()) { + return; + } + sendMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.DEVICE_HMS.getCode(), TelemetryDTO.>builder().sn(sn).host(unReadList).build()); } @Override @@ -159,7 +158,7 @@ public class DeviceHmsServiceImpl implements IDeviceHmsService { .eq(DeviceHmsEntity::getSn, deviceSn) .eq(DeviceHmsEntity::getUpdateTime, 0L)); // Delete unread messages cached in redis. - RedisOpsUtils.del(RedisConst.HMS_PREFIX + deviceSn); + deviceRedisService.delHmsKeysBySn(deviceSn); } private DeviceHmsDTO entity2Dto(DeviceHmsEntity entity) { @@ -191,8 +190,12 @@ public class DeviceHmsServiceImpl implements IDeviceHmsService { dto.setLevel(receiver.getLevel()); dto.setModule(receiver.getModule()); dto.setHmsId(UUID.randomUUID().toString()); - - if (HmsEnum.DomainType.DRONE_NEST.getDomain().equals(receiver.getDomainType())) { + Optional domainEnumOpt = Optional.ofNullable(receiver.getDeviceType()) + .map(type -> type.split("-")).map(type -> type[0]).map(Integer::parseInt).map(DeviceDomainEnum::find); + if (domainEnumOpt.isEmpty()) { + throw new RuntimeException("The device type does not match, please check the data."); + } + if (DeviceDomainEnum.DOCK == domainEnumOpt.get()) { dto.setHmsKey(HmsEnum.HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode()); return; } 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 b94a0ad..a5aca9a 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 @@ -10,9 +10,8 @@ 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.component.websocket.model.CustomWebSocketMessage; +import com.dji.sample.component.websocket.model.BizCodeEnum; import com.dji.sample.component.websocket.service.ISendMessageService; -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; @@ -22,6 +21,7 @@ import com.dji.sample.manage.model.param.DeviceLogsQueryParam; import com.dji.sample.manage.model.param.LogsFileUpdateParam; import com.dji.sample.manage.model.receiver.*; import com.dji.sample.manage.service.IDeviceLogsService; +import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.manage.service.ILogsFileService; import com.dji.sample.manage.service.ITopologyService; import com.dji.sample.media.model.StsCredentialsDTO; @@ -69,9 +69,6 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { @Autowired private ILogsFileService logsFileService; - @Autowired - private RedisOpsUtils redisOpsUtils; - @Autowired private IStorageService storageService; @@ -82,7 +79,7 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { private ISendMessageService webSocketMessageService; @Autowired - private IWebSocketManageService webSocketManageService; + private IDeviceRedisService deviceRedisService; @Override public PaginationData getUploadedLogs(String deviceSn, DeviceLogsQueryParam param) { @@ -104,29 +101,21 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { @Override public ResponseResult getRealTimeLogs(String deviceSn, List domainList) { - boolean exist = redisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceSn) > 0; + boolean exist = deviceRedisService.checkDeviceOnline(deviceSn); if (!exist) { return ResponseResult.error("Device is offline."); } - String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF; - LogsFileUploadList data = messageSenderService.publishWithReply( - LogsFileUploadList.class, - topic, - CommonTopicResponse.builder() - .tid(UUID.randomUUID().toString()) - .bid(UUID.randomUUID().toString()) - .method(LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod()) - .timestamp(System.currentTimeMillis()) - .data(Map.of(MapKeyConst.MODULE_LIST, domainList)) - .build(), 1); - - for (LogsFileUpload file : data.getFiles()) { + ServiceReply> data = messageSenderService.publishServicesTopic( + new TypeReference>() {}, deviceSn, LogsFileMethodEnum.FILE_UPLOAD_LIST.getMethod(), + Map.of(MapKeyConst.MODULE_LIST, domainList)); + + for (LogsFileUpload file : data.getOutput()) { if (file.getDeviceSn().isBlank()) { file.setDeviceSn(deviceSn); } } - return ResponseResult.success(data); + return ResponseResult.success(new LogsFileUploadList(data.getOutput(), data.getResult())); } @Override @@ -164,15 +153,8 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { credentialsDTO.setParams(LogsFileUploadList.builder().files(files).build()); String bid = UUID.randomUUID().toString(); - 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(LogsFileMethodEnum.FILE_UPLOAD_START.getMethod()) - .data(credentialsDTO) - .build()); + ServiceReply reply = messageSenderService.publishServicesTopic( + deviceSn, LogsFileMethodEnum.FILE_UPLOAD_START.getMethod(), credentialsDTO, bid); if (ResponseResult.CODE_SUCCESS != reply.getResult()) { return ResponseResult.error(String.valueOf(reply.getResult())); @@ -184,7 +166,7 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { } // Save the status of the log upload. - redisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + deviceSn, bid, LogsOutputProgressDTO.builder().logsId(logsId).build()); + RedisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + deviceSn, bid, LogsOutputProgressDTO.builder().logsId(logsId).build()); return ResponseResult.success(); } @@ -195,16 +177,8 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { if (LogsFileUpdateMethodEnum.UNKNOWN == method) { return ResponseResult.error("Illegal param"); } - String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + deviceSn + TopicConst.SERVICES_SUF; - String bid = UUID.randomUUID().toString(); - ServiceReply reply = messageSenderService.publishWithReply(topic, - CommonTopicResponse.builder() - .tid(UUID.randomUUID().toString()) - .bid(bid) - .timestamp(System.currentTimeMillis()) - .method(LogsFileMethodEnum.FILE_UPLOAD_UPDATE.getMethod()) - .data(param) - .build()); + ServiceReply reply = messageSenderService.publishServicesTopic( + deviceSn, LogsFileMethodEnum.FILE_UPLOAD_UPDATE.getMethod(), param); if (ResponseResult.CODE_SUCCESS != reply.getResult()) { return ResponseResult.error("Error Code : " + reply.getResult()); @@ -220,25 +194,13 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { logsFileService.deleteFileByLogsId(logsId); } - @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS, outputChannel = ChannelName.OUTBOUND) + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS) @Override - public void handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers) { + public CommonTopicReceiver handleFileUploadProgress(CommonTopicReceiver receiver, MessageHeaders headers) { String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); String sn = topic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(), topic.indexOf(TopicConst.EVENTS_SUF)); - if (receiver.getNeedReply() != null && receiver.getNeedReply() == 1) { - String replyTopic = headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF; - messageSenderService.publish(replyTopic, - CommonTopicResponse.builder() - .tid(receiver.getTid()) - .bid(receiver.getBid()) - .method(receiver.getMethod()) - .timestamp(System.currentTimeMillis()) - .data(RequestsReply.success()) - .build()); - } - EventsReceiver eventsReceiver = objectMapper.convertValue(receiver.getData(), new TypeReference>(){}); @@ -246,7 +208,12 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { webSocketData.setBid(receiver.getBid()); webSocketData.setSn(sn); - DeviceDTO device = (DeviceDTO) redisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn); + Optional deviceOpt = deviceRedisService.getDeviceOnline(sn); + if (deviceOpt.isEmpty()) { + return null; + } + + DeviceDTO device = deviceOpt.get(); try { OutputLogsProgressReceiver output = eventsReceiver.getOutput(); @@ -255,12 +222,12 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { String key = RedisConst.LOGS_FILE_PREFIX + sn; LogsOutputProgressDTO progress; - boolean exist = redisOpsUtils.checkExist(key); + boolean exist = RedisOpsUtils.checkExist(key); if (!exist && !statusEnum.getEnd()) { progress = LogsOutputProgressDTO.builder().logsId(receiver.getBid()).build(); - redisOpsUtils.hashSet(key, receiver.getBid(), progress); + RedisOpsUtils.hashSet(key, receiver.getBid(), progress); } else if (exist) { - progress = (LogsOutputProgressDTO) redisOpsUtils.hashGet(key, receiver.getBid()); + progress = (LogsOutputProgressDTO) RedisOpsUtils.hashGet(key, receiver.getBid()); } else { progress = LogsOutputProgressDTO.builder().build(); } @@ -269,7 +236,7 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { // If the logs file is empty, delete the cache of this task. List fileReceivers = output.getExt().getFiles(); if (CollectionUtils.isEmpty(fileReceivers)) { - redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); + RedisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); } // refresh cache. @@ -295,10 +262,10 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { }); progress.setFiles(fileProgressList); webSocketData.setOutput(progress); - redisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + sn, receiver.getBid(), progress); + RedisOpsUtils.hashSet(RedisConst.LOGS_FILE_PREFIX + sn, receiver.getBid(), progress); // Delete the cache at the end of the task. if (statusEnum.getEnd()) { - redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); + RedisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); this.updateLogsStatus(receiver.getBid(), DeviceLogsStatusEnum.find(statusEnum).getVal()); fileReceivers.forEach(file -> logsFileService.updateFile(receiver.getBid(), file)); @@ -306,18 +273,13 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { } catch (NullPointerException e) { this.updateLogsStatus(receiver.getBid(), DeviceLogsStatusEnum.FAILED.getVal()); - redisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); + RedisOpsUtils.del(RedisConst.LOGS_FILE_PREFIX + sn); } - webSocketMessageService.sendBatch( - webSocketManageService.getValueWithWorkspaceAndUserType( - device.getWorkspaceId(), UserTypeEnum.WEB.getVal()), - CustomWebSocketMessage.builder() - .data(webSocketData) - .timestamp(System.currentTimeMillis()) - .bizCode(receiver.getMethod()) - .build()); + webSocketMessageService.sendBatch(device.getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.FILE_UPLOAD_PROGRESS.getCode(), webSocketData); + return receiver; } @Override @@ -340,9 +302,9 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { return null; } String key = RedisConst.LOGS_FILE_PREFIX + entity.getDeviceSn(); - LogsOutputProgressDTO progress = new LogsOutputProgressDTO(); - if (redisOpsUtils.hashCheck(key, entity.getLogsId())) { - progress = (LogsOutputProgressDTO) redisOpsUtils.hashGet(key, entity.getLogsId()); + LogsOutputProgressDTO progress = null; + if (RedisOpsUtils.hashCheck(key, entity.getLogsId())) { + progress = (LogsOutputProgressDTO) RedisOpsUtils.hashGet(key, entity.getLogsId()); } return DeviceLogsDTO.builder() @@ -354,7 +316,7 @@ public class DeviceLogsServiceImpl implements IDeviceLogsService { .logsInformation(entity.getLogsInfo()) .userName(entity.getUsername()) .deviceLogs(LogsFileUploadList.builder().files(logsFileService.getLogsFileByLogsId(entity.getLogsId())).build()) - .logsProgress(progress.getFiles()) + .logsProgress(Objects.requireNonNullElse(progress, new LogsOutputProgressDTO()).getFiles()) .deviceTopo(topologyService.getDeviceTopologyByGatewaySn(entity.getDeviceSn()).orElse(null)) .build(); } 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 b540e08..e5a596e 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 @@ -68,7 +68,7 @@ public class DeviceOSDServiceImpl extends AbstractTSAService { Map receiverData = (Map) receiver.getData(); data.setPayloads(payloadsList.stream() .map(payload -> mapper.convertValue( - receiverData.getOrDefault(payload.getPayloadName(), Map.of()), + receiverData.getOrDefault(payload.getPayloadIndex(), Map.of()), OsdPayloadReceiver.class)) .collect(Collectors.toList())); 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 89a7308..04169bd 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 @@ -2,21 +2,31 @@ package com.dji.sample.manage.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.dji.sample.component.mqtt.model.ChannelName; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; +import com.dji.sample.component.websocket.model.BizCodeEnum; +import com.dji.sample.component.websocket.service.ISendMessageService; +import com.dji.sample.control.model.enums.DroneAuthorityEnum; import com.dji.sample.manage.dao.IDevicePayloadMapper; +import com.dji.sample.manage.model.dto.DeviceAuthorityDTO; 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.ControlSourceEnum; import com.dji.sample.manage.model.enums.DeviceDomainEnum; +import com.dji.sample.manage.model.enums.UserTypeEnum; import com.dji.sample.manage.model.receiver.DevicePayloadReceiver; import com.dji.sample.manage.model.receiver.FirmwareVersionReceiver; import com.dji.sample.manage.service.ICapacityCameraService; import com.dji.sample.manage.service.IDeviceDictionaryService; import com.dji.sample.manage.service.IDevicePayloadService; +import com.dji.sample.manage.service.IDeviceRedisService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -43,6 +53,12 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { @Autowired private ICapacityCameraService capacityCameraService; + @Autowired + private ISendMessageService sendMessageService; + + @Autowired + private IDeviceRedisService deviceRedisService; + @Override public Integer checkPayloadExist(String payloadSn) { DevicePayloadEntity devicePayload = mapper.selectOne( @@ -56,7 +72,9 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { // If it already exists, update the data directly. if (id > 0) { entity.setId(id); - return mapper.updateById(entity); + // For the payload of the drone itself, there is no firmware version. + entity.setFirmwareVersion(null); + return mapper.updateById(entity) > 0 ? entity.getId() : 0; } return mapper.insert(entity) > 0 ? entity.getId() : 0; } @@ -68,23 +86,41 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { } String deviceSn = payloadReceiverList.get(0).getDeviceSn(); - String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn; - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(key); + Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceSn); + if (deviceOpt.isEmpty()) { + return false; + } + DeviceDTO device = deviceOpt.get(); List payloads = new ArrayList<>(); + Map controlMap = CollectionUtils.isEmpty(device.getPayloadsList()) ? + Collections.emptyMap() : + device.getPayloadsList().stream() + .collect(Collectors.toMap(DevicePayloadDTO::getPayloadIndex, DevicePayloadDTO::getControlSource)); + for (DevicePayloadReceiver payloadReceiver : payloadReceiverList) { int payloadId = this.saveOnePayloadDTO(payloadReceiver); if (payloadId <= 0) { return false; } payloads.add(this.receiver2Dto(payloadReceiver)); + if (!controlMap.getOrDefault(payloadReceiver.getPayloadIndex(), "").equals(payloadReceiver.getControlSource())) { + sendMessageService.sendBatch(device.getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.CONTROL_SOURCE_CHANGE.getCode(), + DeviceAuthorityDTO.builder() + .controlSource(payloadReceiver.getControlSource()) + .sn(payloadReceiver.getSn()) + .type(DroneAuthorityEnum.PAYLOAD) + .build()); + } } if (payloads.isEmpty()) { payloads = this.getDevicePayloadEntitiesByDeviceSn(deviceSn); } device.setPayloadsList(payloads); - RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), device, RedisConst.DEVICE_ALIVE_SECOND); + + deviceRedisService.setDeviceOnline(device); return true; } @@ -122,33 +158,35 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { .eq(DevicePayloadEntity::getDeviceSn, receiver.getSn())); } - @Override - public void saveDeviceBasicPayload(List payloadReceiverList, Long timestamp) { + /** + * Handle payload data for devices. + * @param payloadReceiverList + * @param headers + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_PAYLOAD) + public void handleDeviceBasicPayload(List payloadReceiverList, MessageHeaders headers) { if (payloadReceiverList.isEmpty()) { return; } - String deviceSn = payloadReceiverList.stream().findAny().get().getDeviceSn(); + String deviceSn = payloadReceiverList.get(0).getDeviceSn(); String key = RedisConst.STATE_PAYLOAD_PREFIX + deviceSn; // Solve timing problems long last = (long) Objects.requireNonNullElse(RedisOpsUtils.get(key), 0L); + long timestamp = headers.getTimestamp(); if (last > timestamp) { return; } - // Filter unsaved payload information. Set payloadSns = this.getDevicePayloadEntitiesByDeviceSn(payloadReceiverList.get(0).getDeviceSn()) .stream().map(DevicePayloadDTO::getPayloadSn).collect(Collectors.toSet()); Set newPayloadSns = payloadReceiverList.stream().map(DevicePayloadReceiver::getSn).collect(Collectors.toSet()); - Set needToDel = payloadSns.stream().filter(sn -> !newPayloadSns.contains(sn)).collect(Collectors.toSet()); - this.deletePayloadsByPayloadsSn(needToDel); - - List needToSave = payloadReceiverList.stream() - .filter(payload -> !payloadSns.contains(payload.getSn())).collect(Collectors.toList()); + payloadSns.removeAll(newPayloadSns); + this.deletePayloadsByPayloadsSn(payloadSns); // Save the new payload information. - boolean isSave = this.savePayloadDTOs(needToSave); + boolean isSave = this.savePayloadDTOs(payloadReceiverList); if (isSave) { RedisOpsUtils.setWithExpire(key, timestamp, RedisConst.DEVICE_ALIVE_SECOND); } @@ -164,6 +202,19 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { .or(wrapper -> payloadSns.forEach(sn -> wrapper.eq(DevicePayloadEntity::getPayloadSn, sn)))); } + @Override + public Boolean checkAuthorityPayload(String deviceSn, String payloadIndex) { + return deviceRedisService.getDeviceOnline(deviceSn).flatMap(device -> + Optional.of(DeviceDomainEnum.SUB_DEVICE.getVal() == device.getDomain() + && !CollectionUtils.isEmpty(device.getPayloadsList()) + && ControlSourceEnum.A.getControlSource() + .equals(device.getPayloadsList().stream() + .filter(payload -> payloadIndex.equals(payload.getPayloadIndex())) + .map(DevicePayloadDTO::getControlSource).findAny() + .orElse(ControlSourceEnum.B.getControlSource())))).orElse(true); + + } + /** * Convert database entity objects into payload data transfer object. * @param entity @@ -175,7 +226,9 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { builder.payloadSn(entity.getPayloadSn()) .payloadName(entity.getPayloadName()) .payloadDesc(entity.getPayloadDesc()) - .payloadIndex(entity.getPayloadIndex()); + .index(entity.getPayloadIndex()) + .payloadIndex(entity.getPayloadType() + "-" + entity.getSubType() + "-" + entity.getPayloadIndex()) + .controlSource(entity.getControlSource()); } return builder.build(); } @@ -196,25 +249,23 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { String[] payloadIndexArr = dto.getPayloadIndex().split("-"); try { int[] arr = Arrays.stream(payloadIndexArr) - .map(Integer::valueOf) - .mapToInt(Integer::intValue) + .mapToInt(Integer::parseInt) .toArray(); - if (arr.length == 3) { - Optional dictionaryOpt = dictionaryService - .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), arr[0], arr[1]); - dictionaryOpt.ifPresent(dictionary -> - builder.payloadName(dictionary.getDeviceName()) - .payloadDesc(dictionary.getDeviceDesc())); + Optional dictionaryOpt = dictionaryService + .getOneDictionaryInfoByTypeSubType(DeviceDomainEnum.PAYLOAD.getVal(), arr[0], arr[1]); + dictionaryOpt.ifPresent(dictionary -> + builder.payloadName(dictionary.getDeviceName()) + .payloadDesc(dictionary.getDeviceDesc())); - } builder.payloadType(arr[0]) .subType(arr[1]) - .payloadIndex(arr[2]); + .payloadIndex(arr[2]) + .controlSource(dto.getControlSource()); } catch (NumberFormatException e) { - builder.payloadType(Integer.valueOf(payloadIndexArr[0])) + builder.payloadType(-1) .subType(-1) - .payloadIndex(Integer.valueOf(payloadIndexArr[2])); + .payloadIndex(-1); } return builder @@ -229,7 +280,8 @@ public class DevicePayloadServiceImpl implements IDevicePayloadService { return builder.build(); } return builder.payloadSn(receiver.getSn()) - .payloadName(receiver.getPayloadIndex()) + .payloadIndex(receiver.getPayloadIndex()) + .controlSource(receiver.getControlSource()) .build(); } diff --git a/src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java b/src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java new file mode 100644 index 0000000..4629892 --- /dev/null +++ b/src/main/java/com/dji/sample/manage/service/impl/DeviceRedisServiceImpl.java @@ -0,0 +1,80 @@ +package com.dji.sample.manage.service.impl; + +import com.dji.sample.component.mqtt.model.EventsOutputProgressReceiver; +import com.dji.sample.component.mqtt.model.EventsReceiver; +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.model.receiver.FirmwareProgressExtReceiver; +import com.dji.sample.manage.service.IDeviceRedisService; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/21 + */ +@Service +public class DeviceRedisServiceImpl implements IDeviceRedisService { + + @Override + public Boolean checkDeviceOnline(String sn) { + String key = RedisConst.DEVICE_ONLINE_PREFIX + sn; + return RedisOpsUtils.checkExist(key) && RedisOpsUtils.getExpire(key) > 0; + } + + @Override + public Optional getDeviceOnline(String sn) { + return Optional.ofNullable((DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + sn)); + } + + @Override + public void setDeviceOnline(DeviceDTO device) { + RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), device, RedisConst.DEVICE_ALIVE_SECOND); + } + + @Override + public Boolean delDeviceOnline(String sn) { + return RedisOpsUtils.del(RedisConst.DEVICE_ONLINE_PREFIX + sn); + } + + @Override + public Optional getDeviceOsd(String sn, Class clazz) { + return Optional.ofNullable(clazz.cast(RedisOpsUtils.get(RedisConst.OSD_PREFIX + sn))); + } + + @Override + public void setFirmwareUpgrading(String sn, EventsReceiver> events) { + RedisOpsUtils.setWithExpire(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn, events, RedisConst.DEVICE_ALIVE_SECOND * 20); + } + + @Override + public Optional>> getFirmwareUpgradingProgress(String sn) { + return Optional.ofNullable((EventsReceiver>) RedisOpsUtils.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn)); + } + + @Override + public Boolean delFirmwareUpgrading(String sn) { + return RedisOpsUtils.del(RedisConst.FIRMWARE_UPGRADING_PREFIX + sn); + } + + @Override + public void addEndHmsKeys(String sn, String... keys) { + RedisOpsUtils.listRPush(RedisConst.HMS_PREFIX + sn, keys); + } + + @Override + public Set getAllHmsKeys(String sn) { + return RedisOpsUtils.listGetAll(RedisConst.HMS_PREFIX + sn).stream() + .map(String::valueOf).collect(Collectors.toSet()); + } + + @Override + public Boolean delHmsKeysBySn(String sn) { + return RedisOpsUtils.del(RedisConst.HMS_PREFIX + sn); + } +} 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 6677ca8..809fd7f 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 @@ -17,6 +17,7 @@ import com.dji.sample.component.websocket.model.BizCodeEnum; 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.DroneAuthorityEnum; import com.dji.sample.manage.dao.IDeviceMapper; import com.dji.sample.manage.model.dto.*; import com.dji.sample.manage.model.entity.DeviceEntity; @@ -91,6 +92,12 @@ public class DeviceServiceImpl implements IDeviceService { @Autowired private IDeviceFirmwareService deviceFirmwareService; + @Autowired + private ICapacityCameraService capacityCameraService; + + @Autowired + private IDeviceRedisService deviceRedisService; + @Autowired @Qualifier("gatewayOSDServiceImpl") private ITSAService tsaService; @@ -104,24 +111,23 @@ public class DeviceServiceImpl implements IDeviceService { this.subscribeTopicOnline(gatewaySn); // Only the remote controller is logged in and the aircraft is not connected. - String key = RedisConst.DEVICE_ONLINE_PREFIX + gatewaySn; - - boolean exist = RedisOpsUtils.checkExist(key); - if (!exist) { + Optional deviceOpt = deviceRedisService.getDeviceOnline(gatewaySn); + if (deviceOpt.isEmpty()) { Optional gatewayOpt = this.getDeviceBySn(gatewaySn); if (gatewayOpt.isPresent()) { DeviceDTO value = gatewayOpt.get(); - RedisOpsUtils.setWithExpire(key, value, RedisConst.DEVICE_ALIVE_SECOND); + value.setChildDeviceSn(null); + deviceRedisService.setDeviceOnline(value); this.pushDeviceOnlineTopo(value.getWorkspaceId(), gatewaySn, gatewaySn); return true; } // When connecting for the first time DeviceEntity gatewayDevice = deviceGatewayConvertToDeviceEntity(gateway); - return onlineSaveDevice(gatewayDevice, null).isPresent(); + return onlineSaveDevice(gatewayDevice, null, null).isPresent(); } - DeviceDTO deviceDTO = (DeviceDTO) (RedisOpsUtils.get(key)); + DeviceDTO deviceDTO = deviceOpt.get(); String deviceSn = deviceDTO.getChildDeviceSn(); if (!StringUtils.hasText(deviceSn)) { return true; @@ -134,22 +140,22 @@ public class DeviceServiceImpl implements IDeviceService { public Boolean subDeviceOffline(String deviceSn) { // 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 (!RedisOpsUtils.checkExist(key) || RedisOpsUtils.getExpire(key) <= 0) { + Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceSn); + if (deviceOpt.isEmpty()) { log.debug("The drone is already offline."); return true; } - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(key); + DeviceDTO device = deviceOpt.get(); // Cancel drone-related subscriptions. this.unsubscribeTopicOffline(deviceSn); - payloadService.deletePayloadsByDeviceSn(new ArrayList<>(List.of(deviceSn))); + capacityCameraService.deleteCapacityCameraByDeviceSn(deviceSn); + deviceRedisService.delDeviceOnline(deviceSn); + RedisOpsUtils.del(RedisConst.OSD_PREFIX + deviceSn); + deviceRedisService.delHmsKeysBySn(deviceSn); // Publish the latest device topology information in the current workspace. this.pushDeviceOfflineTopo(device.getWorkspaceId(), deviceSn); - RedisOpsUtils.del(key); - RedisOpsUtils.del(RedisConst.OSD_PREFIX + device.getDeviceSn()); - RedisOpsUtils.del(RedisConst.HMS_PREFIX + device.getDeviceSn()); log.debug("{} offline.", deviceSn); return true; } @@ -157,13 +163,11 @@ public class DeviceServiceImpl implements IDeviceService { @Override public Boolean deviceOnline(StatusGatewayReceiver deviceGateway) { String deviceSn = deviceGateway.getSubDevices().get(0).getSn(); - String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn; - // change log: Use redis instead of - long time = RedisOpsUtils.getExpire(key); - long gatewayTime = RedisOpsUtils.getExpire(RedisConst.DEVICE_ONLINE_PREFIX + deviceGateway.getSn()); - if (time > 0 && gatewayTime > 0) { - RedisOpsUtils.expireKey(key, RedisConst.DEVICE_ALIVE_SECOND); + Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceSn); + Optional gatewayOpt = deviceRedisService.getDeviceOnline(deviceGateway.getSn()); + + if (deviceOpt.isPresent() && gatewayOpt.isPresent()) { DeviceDTO device = DeviceDTO.builder().loginTime(LocalDateTime.now()).deviceSn(deviceSn).build(); DeviceDTO gateway = DeviceDTO.builder() .loginTime(LocalDateTime.now()) @@ -171,7 +175,7 @@ public class DeviceServiceImpl implements IDeviceService { .childDeviceSn(deviceSn).build(); this.updateDevice(gateway); this.updateDevice(device); - String workspaceId = ((DeviceDTO)(RedisOpsUtils.get(key))).getWorkspaceId(); + String workspaceId = deviceOpt.get().getWorkspaceId(); if (StringUtils.hasText(workspaceId)) { this.subscribeTopicOnline(deviceSn); this.subscribeTopicOnline(deviceGateway.getSn()); @@ -193,14 +197,14 @@ public class DeviceServiceImpl implements IDeviceService { }); DeviceEntity gateway = deviceGatewayConvertToDeviceEntity(deviceGateway); - Optional gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn); + Optional gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn, null); if (gatewayEntityOpt.isEmpty()) { log.error("Failed to go online, please check the status data or code logic."); return false; } DeviceEntity subDevice = subDeviceConvertToDeviceEntity(deviceGateway.getSubDevices().get(0)); - Optional subDeviceEntityOpt = onlineSaveDevice(subDevice, null); + Optional subDeviceEntityOpt = onlineSaveDevice(subDevice, null, gateway.getDeviceSn()); if (subDeviceEntityOpt.isEmpty()) { log.error("Failed to go online, please check the status data or code logic."); return false; @@ -308,7 +312,7 @@ public class DeviceServiceImpl implements IDeviceService { devicesList.stream() .filter(gateway -> DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() || - RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn())) + deviceRedisService.checkDeviceOnline(gateway.getDeviceSn())) .forEach(this::spliceDeviceTopo); return devicesList; @@ -317,7 +321,7 @@ public class DeviceServiceImpl implements IDeviceService { @Override public void spliceDeviceTopo(DeviceDTO gateway) { - gateway.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn())); + gateway.setStatus(deviceRedisService.checkDeviceOnline(gateway.getDeviceSn())); // sub device if (!StringUtils.hasText(gateway.getChildDeviceSn())) { @@ -325,7 +329,7 @@ public class DeviceServiceImpl implements IDeviceService { } DeviceDTO subDevice = getDevicesByParams(DeviceQueryParam.builder().deviceSn(gateway.getChildDeviceSn()).build()).get(0); - subDevice.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + subDevice.getDeviceSn())); + subDevice.setStatus(deviceRedisService.checkDeviceOnline(subDevice.getDeviceSn())); gateway.setChildren(subDevice); // payloads @@ -362,8 +366,7 @@ public class DeviceServiceImpl implements IDeviceService { this.getDeviceTopoForPilot(sn) .ifPresent(pilotMessage::setData); - boolean exist = RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn); - pilotMessage.getData().setOnlineStatus(exist); + pilotMessage.getData().setOnlineStatus(deviceRedisService.checkDeviceOnline(sn)); pilotMessage.getData().setGatewaySn(gatewaySn.equals(sn) ? "" : gatewaySn); sendMessageService.sendBatch(sessions, pilotMessage); @@ -383,7 +386,7 @@ public class DeviceServiceImpl implements IDeviceService { .key(device.getDomain() + "-" + device.getType() + "-" + device.getSubType()) .build()) .iconUrls(device.getIconUrl()) - .onlineStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn())) + .onlineStatus(deviceRedisService.checkDeviceOnline(device.getDeviceSn())) .boundStatus(device.getBoundStatus()) .model(device.getDeviceName()) .userId(device.getUserId()) @@ -427,25 +430,24 @@ public class DeviceServiceImpl implements IDeviceService { String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(), topic.indexOf(OSD_SUF)); - // Real-time update of device status in memory - RedisOpsUtils.expireKey(RedisConst.DEVICE_ONLINE_PREFIX + from, RedisConst.DEVICE_ALIVE_SECOND); + Optional deviceOpt = deviceRedisService.getDeviceOnline(from); - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + from); - - if (device == null) { - Optional deviceOpt = this.getDeviceBySn(from); + if (deviceOpt.isEmpty()) { + deviceOpt = this.getDeviceBySn(from); if (deviceOpt.isEmpty()) { + log.error("Please restart the drone."); return; } - device = deviceOpt.get(); - if (!StringUtils.hasText(device.getWorkspaceId())) { + + if (!StringUtils.hasText(deviceOpt.get().getWorkspaceId())) { this.unsubscribeTopicOffline(from); return; } - RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + from, device, - RedisConst.DEVICE_ALIVE_SECOND); + deviceRedisService.setDeviceOnline(deviceOpt.get()); this.subscribeTopicOnline(from); } + DeviceDTO device = deviceOpt.get(); + deviceRedisService.setDeviceOnline(device); receiver = objectMapper.readValue(payload, CommonTopicReceiver.class); @@ -550,6 +552,7 @@ public class DeviceServiceImpl implements IDeviceService { .version(gateway.getVersion()) .domain(gateway.getDomain() != null ? gateway.getDomain() : DeviceDomainEnum.GATEWAY.getVal()) + .deviceIndex(gateway.getSubDevices().isEmpty() ? null : gateway.getSubDevices().get(0).getIndex()) .build(); } @@ -578,7 +581,6 @@ public class DeviceServiceImpl implements IDeviceService { .deviceType(device.getType()) .subType(device.getSubType()) .version(device.getVersion()) - .deviceIndex(device.getIndex()) .domain(DeviceDomainEnum.SUB_DEVICE.getVal()) .build(); } @@ -592,12 +594,12 @@ public class DeviceServiceImpl implements IDeviceService { if (entity == null) { return null; } - DeviceDTO.DeviceDTOBuilder deviceDTOBuilder = DeviceDTO.builder() + DeviceDTO deviceDTO = DeviceDTO.builder() .deviceSn(entity.getDeviceSn()) .childDeviceSn(entity.getChildSn()) .deviceName(entity.getDeviceName()) .deviceDesc(entity.getDeviceDesc()) - .deviceIndex(entity.getDeviceIndex()) + .controlSource(entity.getDeviceIndex()) .workspaceId(entity.getWorkspaceId()) .type(entity.getDeviceType()) .subType(entity.getSubType()) @@ -617,31 +619,43 @@ public class DeviceServiceImpl implements IDeviceService { .firmwareVersion(entity.getFirmwareVersion()) .workspaceName(entity.getWorkspaceId() != null ? workspaceService.getWorkspaceByWorkspaceId(entity.getWorkspaceId()) - .map(WorkspaceDTO::getWorkspaceName).orElse("") : ""); + .map(WorkspaceDTO::getWorkspaceName).orElse("") : "") + .firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build(); + addFirmwareStatus(deviceDTO, entity); + return deviceDTO; + } + + private void addFirmwareStatus(DeviceDTO deviceDTO, DeviceEntity entity) { if (!StringUtils.hasText(entity.getFirmwareVersion())) { - return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build(); + return; } // Query whether the device is updating firmware. - Object progress = RedisOpsUtils.get(RedisConst.FIRMWARE_UPGRADING_PREFIX + entity.getDeviceSn()); - if (Objects.nonNull(progress)) { - return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal()).firmwareProgress((int)progress).build(); + Optional>> progressOpt = + deviceRedisService.getFirmwareUpgradingProgress(entity.getDeviceSn()); + if (progressOpt.isPresent()) { + deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.UPGRADING.getVal()); + deviceDTO.setFirmwareProgress(progressOpt.map(EventsReceiver::getOutput) + .map(EventsOutputProgressReceiver::getProgress) + .map(OutputProgressReceiver::getPercent) + .orElse(0)); + return; } // First query the latest firmware version of the device model and compare it with the current firmware version // to see if it needs to be upgraded. Optional firmwareReleaseNoteOpt = deviceFirmwareService.getLatestFirmwareReleaseNote(entity.getDeviceName()); - if (firmwareReleaseNoteOpt.isPresent()) { - DeviceFirmwareNoteDTO firmwareNoteDTO = firmwareReleaseNoteOpt.get(); - if (firmwareNoteDTO.getProductVersion().equals(entity.getFirmwareVersion())) { - return deviceDTOBuilder.firmwareStatus(entity.getCompatibleStatus() ? - DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() : - DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal()).build(); - } - - return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal()).build(); + if (firmwareReleaseNoteOpt.isEmpty()) { + deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()); + return; } - return deviceDTOBuilder.firmwareStatus(DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal()).build(); + if (entity.getFirmwareVersion().equals(firmwareReleaseNoteOpt.get().getProductVersion())) { + deviceDTO.setFirmwareStatus(entity.getCompatibleStatus() ? + DeviceFirmwareStatusEnum.NOT_UPGRADE.getVal() : + DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE.getVal()); + return; + } + deviceDTO.setFirmwareStatus(DeviceFirmwareStatusEnum.NORMAL_UPGRADE.getVal()); } @Override @@ -661,14 +675,14 @@ public class DeviceServiceImpl implements IDeviceService { return false; } - String key = RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(); - if (!RedisOpsUtils.checkExist(key)) { + Optional deviceOpt = deviceRedisService.getDeviceOnline(device.getDeviceSn()); + if (deviceOpt.isEmpty()) { return false; } - DeviceDTO redisDevice = (DeviceDTO)RedisOpsUtils.get(key); + DeviceDTO redisDevice = deviceOpt.get(); redisDevice.setWorkspaceId(device.getWorkspaceId()); - RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND); + deviceRedisService.setDeviceOnline(redisDevice); if (DeviceDomainEnum.GATEWAY.getVal() == redisDevice.getDomain()) { this.pushDeviceOnlineTopo(webSocketManageService.getValueWithWorkspace(device.getWorkspaceId()), @@ -686,7 +700,11 @@ public class DeviceServiceImpl implements IDeviceService { return true; } - @Override + /** + * Handle dock binding status requests. + * @param receiver + * @param headers + */ @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_BIND_STATUS, outputChannel = ChannelName.OUTBOUND) public void bindStatus(CommonTopicReceiver receiver, MessageHeaders headers) { List> data = ((Map>>) receiver.getData()).get(MapKeyConst.DEVICES); @@ -715,7 +733,11 @@ public class DeviceServiceImpl implements IDeviceService { } - @Override + /** + * Handle dock binding requests. + * @param receiver + * @param headers + */ @ServiceActivator(inputChannel = ChannelName.INBOUND_REQUESTS_AIRPORT_ORGANIZATION_BIND) public void bindDevice(CommonTopicReceiver receiver, MessageHeaders headers) { Map> data = objectMapper.convertValue(receiver.getData(), @@ -733,8 +755,6 @@ public class DeviceServiceImpl implements IDeviceService { } } - assert dock != null; - Optional dockEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.DOCK.getVal(), dock); Optional droneEntityOpt = this.bindDevice2Entity(DeviceDomainEnum.SUB_DEVICE.getVal(), drone); @@ -780,12 +800,11 @@ public class DeviceServiceImpl implements IDeviceService { .eq(DeviceEntity::getBoundStatus, true)); List devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO) .peek(device -> { - device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn())); + device.setStatus(deviceRedisService.checkDeviceOnline(device.getDeviceSn())); if (StringUtils.hasText(device.getChildDeviceSn())) { Optional childOpt = this.getDeviceBySn(device.getChildDeviceSn()); childOpt.ifPresent(child -> { - child.setStatus( - RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + child.getDeviceSn())); + child.setStatus(deviceRedisService.checkDeviceOnline(child.getDeviceSn())); child.setWorkspaceName(device.getWorkspaceName()); device.setChildren(child); }); @@ -797,11 +816,16 @@ public class DeviceServiceImpl implements IDeviceService { @Override public void unbindDevice(String deviceSn) { - String key = RedisConst.DEVICE_ONLINE_PREFIX + deviceSn; - DeviceDTO redisDevice = (DeviceDTO) RedisOpsUtils.get(key); - redisDevice.setWorkspaceId(""); - RedisOpsUtils.setWithExpire(key, redisDevice, RedisConst.DEVICE_ALIVE_SECOND); + Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceSn); + if (deviceOpt.isPresent()) { + subDeviceOffline(deviceSn); + } else { + deviceOpt = getDeviceBySn(deviceSn); + } + if (deviceOpt.isEmpty()) { + return; + } DeviceDTO device = DeviceDTO.builder() .deviceSn(deviceSn) .workspaceId("") @@ -818,11 +842,14 @@ public class DeviceServiceImpl implements IDeviceService { return Optional.empty(); } DeviceDTO device = devicesList.get(0); - device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + sn)); + device.setStatus(deviceRedisService.checkDeviceOnline(sn)); return Optional.of(device); } - @Override + /** + * Update the firmware version information of the device or payload. + * @param receiver + */ @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. @@ -851,43 +878,59 @@ public class DeviceServiceImpl implements IDeviceService { return ResponseResult.error(); } - DeviceOtaCreateParam deviceOtaFirmware = deviceOtaFirmwares.get(0); - List devices = getDevicesByParams(DeviceQueryParam.builder().childSn(deviceOtaFirmware.getSn()).build()); - String gatewaySn = devices.isEmpty() ? deviceOtaFirmware.getSn() : devices.get(0).getDeviceSn(); + Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceOtaFirmwares.get(0).getSn()); + if (deviceOpt.isEmpty()) { + throw new RuntimeException("Device is offline."); + } + DeviceDTO device = deviceOpt.get(); + String gatewaySn = DeviceDomainEnum.DOCK.getVal() == device.getDomain() ? device.getDeviceSn() : device.getParentSn(); - String topic = THING_MODEL_PRE + PRODUCT + gatewaySn + SERVICES_SUF; + checkOtaConditions(gatewaySn); - // The bids in the progress messages reported subsequently are the same. String bid = UUID.randomUUID().toString(); - ServiceReply serviceReply = messageSender.publishWithReply( - topic, CommonTopicResponse.>>builder() - .tid(UUID.randomUUID().toString()) - .bid(bid) - .timestamp(System.currentTimeMillis()) - .method(FirmwareMethodEnum.OTA_CREATE.getMethod()) - .data(Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares)) - .build()); + ServiceReply serviceReply = messageSender.publishServicesTopic( + gatewaySn, FirmwareMethodEnum.OTA_CREATE.getMethod(), Map.of(MapKeyConst.DEVICES, deviceOtaFirmwares), bid); if (serviceReply.getResult() != ResponseResult.CODE_SUCCESS) { return ResponseResult.error(serviceReply.getResult(), "Firmware Error Code: " + serviceReply.getResult()); } // Record the device state that needs to be updated. - deviceOtaFirmwares.forEach(deviceOta -> RedisOpsUtils.setWithExpire( - RedisConst.FIRMWARE_UPGRADING_PREFIX + deviceOta.getSn(), - bid, - RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND)); + deviceOtaFirmwares.forEach(deviceOta -> deviceRedisService.setFirmwareUpgrading(deviceOta.getSn(), + EventsReceiver.>builder() + .bid(bid).sn(deviceOta.getSn()).build())); return ResponseResult.success(); } + /** + * Determine whether the firmware can be upgraded. + * @param dockSn + */ + private void checkOtaConditions(String dockSn) { + Optional deviceOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class); + if (deviceOpt.isEmpty()) { + throw new RuntimeException("Dock is offline."); + } + boolean emergencyStopState = deviceOpt.get().getEmergencyStopState(); + if (emergencyStopState) { + throw new RuntimeException("The emergency stop button of the dock is pressed and can't be upgraded."); + } + + DockModeCodeEnum dockMode = this.getDockMode(dockSn); + if (DockModeCodeEnum.IDLE != dockMode) { + throw new RuntimeException("The current status of the dock can't be upgraded."); + } + } + @Override public void devicePropertySet(String workspaceId, String dockSn, DeviceSetPropertyEnum propertyEnum, JsonNode param) { - boolean dockOnline = this.checkDeviceOnline(dockSn); - if (!dockOnline) { + Optional dockOpt = deviceRedisService.getDeviceOnline(dockSn); + if (dockOpt.isEmpty()) { throw new RuntimeException("Dock is offline."); } - DeviceDTO deviceDTO = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn); - boolean deviceOnline = this.checkDeviceOnline(deviceDTO.getChildDeviceSn()); - if (!deviceOnline) { + String childSn = dockOpt.get().getChildDeviceSn(); + boolean deviceOnline = deviceRedisService.checkDeviceOnline(childSn); + Optional osdOpt = deviceRedisService.getDeviceOsd(childSn, OsdSubDeviceReceiver.class); + if (!deviceOnline || osdOpt.isEmpty()) { throw new RuntimeException("Device is offline."); } @@ -899,7 +942,6 @@ public class DeviceServiceImpl implements IDeviceService { } String topic = THING_MODEL_PRE + PRODUCT + dockSn + PROPERTY_SUF + SET_SUF; - OsdSubDeviceReceiver osd = (OsdSubDeviceReceiver) RedisOpsUtils.get(RedisConst.OSD_PREFIX + deviceDTO.getChildDeviceSn()); if (!param.isObject()) { this.deviceOnePropertySet(topic, propertyEnum, Map.entry(propertyEnum.getProperty(), param)); return; @@ -907,7 +949,7 @@ public class DeviceServiceImpl implements IDeviceService { // 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); + boolean isPublish = basicDeviceProperty.canPublish(node.getKey(), osdOpt.get()); if (!isPublish) { continue; } @@ -929,8 +971,7 @@ public class DeviceServiceImpl implements IDeviceService { .tid(UUID.randomUUID().toString()) .timestamp(System.currentTimeMillis()) .data(value) - .build(), - 2); + .build()); while (true) { reply = (Map) reply.get(value.getKey()); @@ -947,9 +988,32 @@ public class DeviceServiceImpl implements IDeviceService { } - public Boolean checkDeviceOnline(String sn) { - String key = RedisConst.DEVICE_ONLINE_PREFIX + sn; - return RedisOpsUtils.checkExist(key) && RedisOpsUtils.getExpire(key) > 0; + @Override + public DockModeCodeEnum getDockMode(String dockSn) { + return deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class) + .map(OsdDockReceiver::getModeCode).orElse(DockModeCodeEnum.DISCONNECTED); + } + + @Override + public DeviceModeCodeEnum getDeviceMode(String deviceSn) { + return deviceRedisService.getDeviceOsd(deviceSn, OsdSubDeviceReceiver.class) + .map(OsdSubDeviceReceiver::getModeCode).orElse(DeviceModeCodeEnum.DISCONNECTED); + } + + @Override + public Boolean checkDockDrcMode(String dockSn) { + return deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class) + .map(OsdDockReceiver::getDrcState) + .orElse(DockDrcStateEnum.DISCONNECTED) != DockDrcStateEnum.DISCONNECTED; + } + + @Override + public Boolean checkAuthorityFlight(String gatewaySn) { + return deviceRedisService.getDeviceOnline(gatewaySn).flatMap(gateway -> + Optional.of((DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() + || DeviceDomainEnum.GATEWAY.getVal() == gateway.getDomain()) + && ControlSourceEnum.A.getControlSource().equals(gateway.getControlSource()))) + .orElse(true); } /** @@ -1038,7 +1102,7 @@ public class DeviceServiceImpl implements IDeviceService { .build(); } - private Optional onlineSaveDevice(DeviceEntity device, String childSn) { + private Optional onlineSaveDevice(DeviceEntity device, String childSn, String parentSn) { Optional deviceOpt = this.getDeviceBySn(device.getDeviceSn()); if (deviceOpt.isEmpty()) { @@ -1060,19 +1124,46 @@ public class DeviceServiceImpl implements IDeviceService { if (saveDeviceOpt.isEmpty()) { return saveDeviceOpt; } - device.setWorkspaceId(saveDeviceOpt.get().getWorkspaceId()); - - RedisOpsUtils.setWithExpire(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn(), - DeviceDTO.builder() - .deviceSn(device.getDeviceSn()) - .workspaceId(device.getWorkspaceId()) - .childDeviceSn(childSn) - .domain(device.getDomain()) - .type(device.getDeviceType()) - .subType(device.getSubType()) - .build(), - RedisConst.DEVICE_ALIVE_SECOND); + + DeviceDTO redisDevice = this.deviceEntityConvertToDTO(saveDeviceOpt.get()); + redisDevice.setParentSn(parentSn); + + deviceRedisService.setDeviceOnline(redisDevice); return saveDeviceOpt; } + + /** + * Handles messages in the state topic about basic drone data. + * + * Note: Only the data of the drone payload is handled here. You can handle other data from the drone + * according to your business needs. + * @param deviceBasic basic drone data + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_STATE_BASIC, outputChannel = ChannelName.INBOUND_STATE_PAYLOAD) + public List stateBasic(DeviceBasicReceiver deviceBasic) { + Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceBasic.getDeviceSn()); + if (deviceOpt.isEmpty()) { + return deviceBasic.getPayloads(); + } + Optional dockOpt = deviceRedisService.getDeviceOnline(deviceOpt.get().getParentSn()); + if (dockOpt.isEmpty()) { + return deviceBasic.getPayloads(); + } + DeviceDTO dock = dockOpt.get(); + if (!deviceBasic.getControlSource().equals(dock.getControlSource())) { + dock.setControlSource(deviceBasic.getControlSource()); + deviceRedisService.setDeviceOnline(dock); + + sendMessageService.sendBatch(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.CONTROL_SOURCE_CHANGE.getCode(), + DeviceAuthorityDTO.builder() + .controlSource(dock.getControlSource()) + .sn(dock.getDeviceSn()) + .type(DroneAuthorityEnum.FLIGHT) + .build()); + + } + return deviceBasic.getPayloads(); + } } \ 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 36cd9b3..afb9fc0 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 @@ -1,6 +1,8 @@ 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.redis.RedisOpsUtils; import com.dji.sample.component.websocket.config.ConcurrentWebSocketSession; import com.dji.sample.component.websocket.model.BizCodeEnum; import com.dji.sample.component.websocket.model.CustomWebSocketMessage; @@ -11,6 +13,7 @@ import com.dji.sample.manage.model.receiver.OsdDockReceiver; import org.springframework.stereotype.Service; import java.util.Collection; +import java.util.Objects; /** * @author sean @@ -40,6 +43,20 @@ public class DockOSDServiceImpl extends AbstractTSAService { OsdDockReceiver data = mapper.convertValue(receiver.getData(), OsdDockReceiver.class); wsMessage.getData().setHost(data); sendMessageService.sendBatch(webSessions, wsMessage); + String key = RedisConst.OSD_PREFIX + device.getDeviceSn(); + OsdDockReceiver redisData = (OsdDockReceiver) RedisOpsUtils.get(key); + if (Objects.nonNull(data.getModeCode())) { + if (Objects.nonNull(redisData)) { + data.setDrcState(redisData.getDrcState()); + } + RedisOpsUtils.setWithExpire(key, data, RedisConst.DEVICE_ALIVE_SECOND); + return; + } + + if (Objects.nonNull(data.getDrcState()) && Objects.nonNull(redisData)) { + redisData.setDrcState(data.getDrcState()); + RedisOpsUtils.setWithExpire(key, redisData, RedisConst.DEVICE_ALIVE_SECOND); + } } } } 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 8b441ba..5d2c8cd 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 @@ -15,10 +15,7 @@ import com.dji.sample.manage.model.enums.LiveVideoQualityEnum; import com.dji.sample.manage.model.param.DeviceQueryParam; import com.dji.sample.manage.model.receiver.CapacityDeviceReceiver; import com.dji.sample.manage.model.receiver.LiveCapacityReceiver; -import com.dji.sample.manage.service.ICapacityCameraService; -import com.dji.sample.manage.service.IDeviceService; -import com.dji.sample.manage.service.ILiveStreamService; -import com.dji.sample.manage.service.IWorkspaceService; +import com.dji.sample.manage.service.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -52,6 +49,9 @@ public class LiveStreamServiceImpl implements ILiveStreamService { @Autowired private IMessageSenderService messageSender; + @Autowired + private IDeviceRedisService deviceRedisService; + @Override public List getLiveCapacity(String workspaceId) { @@ -64,7 +64,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { // Query the live capability of each drone. return devicesList.stream() - .filter(device -> RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn())) + .filter(device -> deviceRedisService.checkDeviceOnline(device.getDeviceSn())) .map(device -> CapacityDeviceDTO.builder() .name(Objects.requireNonNullElse(device.getNickname(), device.getDeviceName())) .sn(device.getDeviceSn()) @@ -125,8 +125,8 @@ public class LiveStreamServiceImpl implements ILiveStreamService { .toString()); break; case RTSP: - Object url = Objects.requireNonNullElse(receiveReply.getOutput(), receiveReply.getInfo()); - this.resolveUrlUser(String.valueOf(url), live); + String url = receiveReply.getOutput().toString(); + this.resolveUrlUser(url, live); break; case UNKNOWN: return ResponseResult.error(LiveErrorEnum.URL_TYPE_NOT_SUPPORTED); @@ -207,7 +207,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { response.setMethod(LiveStreamMethodEnum.LIVE_LENS_CHANGE.getMethod()); response.setData(liveParam); - return messageSender.publishWithReply(respTopic, response); + return messageSender.publishWithReply(ServiceReply.class, respTopic, response); } /** @@ -304,7 +304,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { response.setData(liveParam); response.setMethod(LiveStreamMethodEnum.LIVE_START_PUSH.getMethod()); - return messageSender.publishWithReply(topic, response); + return messageSender.publishWithReply(ServiceReply.class, topic, response); } /** @@ -323,7 +323,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { response.setMethod(LiveStreamMethodEnum.LIVE_SET_QUALITY.getMethod()); response.setData(data); - return messageSender.publishWithReply(respTopic, response); + return messageSender.publishWithReply(ServiceReply.class, respTopic, response); } /** @@ -340,7 +340,7 @@ public class LiveStreamServiceImpl implements ILiveStreamService { response.setData(data); response.setMethod(LiveStreamMethodEnum.LIVE_STOP_PUSH.getMethod()); - return messageSender.publishWithReply(topic, response); + return messageSender.publishWithReply(ServiceReply.class, topic, response); } } \ No newline at end of file diff --git a/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java index 8afaf16..1970479 100644 --- a/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java +++ b/src/main/java/com/dji/sample/map/controller/WorkspaceElementController.java @@ -3,9 +3,7 @@ package com.dji.sample.map.controller; import com.dji.sample.common.model.CustomClaim; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.component.websocket.model.BizCodeEnum; -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.map.model.dto.*; import com.dji.sample.map.service.IWorkspaceElementService; import org.springframework.beans.factory.annotation.Autowired; @@ -34,9 +32,6 @@ public class WorkspaceElementController { @Autowired private ISendMessageService sendMessageService; - @Autowired - private IWebSocketManageService webSocketManageService; - /** * In the first connection, pilot will send out this http request to obtain the group element list. * Also, if pilot receives a group refresh instruction from WebSocket, @@ -79,14 +74,8 @@ public class WorkspaceElementController { // Notify all WebSocket connections in this workspace to be updated when an element is created. elementService.getElementByElementId(elementCreate.getId()) - .ifPresent(groupElement -> - sendMessageService.sendBatch( - webSocketManageService.getValueWithWorkspace(workspaceId), - CustomWebSocketMessage.builder() - .timestamp(System.currentTimeMillis()) - .bizCode(BizCodeEnum.MAP_ELEMENT_CREATE.getCode()) - .data(groupElement) - .build())); + .ifPresent(groupElement -> sendMessageService.sendBatch( + workspaceId, BizCodeEnum.MAP_ELEMENT_CREATE.getCode(), groupElement)); return ResponseResult.success(new ConcurrentHashMap<>(Map.of("id", elementCreate.getId()))); } @@ -115,14 +104,8 @@ public class WorkspaceElementController { // Notify all WebSocket connections in this workspace to update when there is an element update. elementService.getElementByElementId(elementId) - .ifPresent(groupElement -> - sendMessageService.sendBatch( - webSocketManageService.getValueWithWorkspace(workspaceId), - CustomWebSocketMessage.builder() - .timestamp(System.currentTimeMillis()) - .bizCode(BizCodeEnum.MAP_ELEMENT_UPDATE.getCode()) - .data(groupElement) - .build())); + .ifPresent(groupElement -> sendMessageService.sendBatch( + workspaceId, BizCodeEnum.MAP_ELEMENT_UPDATE.getCode(), groupElement)); return response; } @@ -144,16 +127,11 @@ public class WorkspaceElementController { // Notify all WebSocket connections in this workspace to update when an element is deleted. if (ResponseResult.CODE_SUCCESS == response.getCode()) { elementOpt.ifPresent(element -> - sendMessageService.sendBatch( - webSocketManageService.getValueWithWorkspace(workspaceId), - CustomWebSocketMessage.builder() - .timestamp(System.currentTimeMillis()) - .bizCode(BizCodeEnum.MAP_ELEMENT_DELETE.getCode()) - .data(WebSocketElementDelDTO.builder() + sendMessageService.sendBatch(workspaceId, BizCodeEnum.MAP_ELEMENT_DELETE.getCode(), + WebSocketElementDelDTO.builder() .elementId(elementId) .groupId(element.getGroupId()) - .build()) - .build())); + .build())); } return response; } @@ -173,14 +151,9 @@ public class WorkspaceElementController { // Notify all WebSocket connections in this workspace to update when elements are deleted. if (ResponseResult.CODE_SUCCESS == response.getCode()) { - sendMessageService.sendBatch( - webSocketManageService.getValueWithWorkspace(workspaceId), - CustomWebSocketMessage.builder() - .timestamp(System.currentTimeMillis()) - .bizCode(BizCodeEnum.MAP_GROUP_REFRESH.getCode()) + sendMessageService.sendBatch(workspaceId, BizCodeEnum.MAP_GROUP_REFRESH.getCode(), // Group ids that need to re-request data - .data(new ConcurrentHashMap(Map.of("ids", new String[]{groupId}))) - .build()); + new ConcurrentHashMap(Map.of("ids", new String[]{groupId}))); } return response; } diff --git a/src/main/java/com/dji/sample/media/service/IMediaService.java b/src/main/java/com/dji/sample/media/service/IMediaService.java index d1b5021..15a80cc 100644 --- a/src/main/java/com/dji/sample/media/service/IMediaService.java +++ b/src/main/java/com/dji/sample/media/service/IMediaService.java @@ -1,8 +1,6 @@ package com.dji.sample.media.service; -import com.dji.sample.component.mqtt.model.CommonTopicReceiver; import com.dji.sample.media.model.FileUploadDTO; -import org.springframework.messaging.MessageHeaders; import java.util.List; @@ -44,17 +42,4 @@ public interface IMediaService { */ List getExistTinyFingerprints(String workspaceId, List tinyFingerprints); - /** - * Handle media files messages reported by dock. - * @param receiver - * @return - */ - void handleFileUploadCallBack(CommonTopicReceiver receiver); - - /** - * Handles the highest priority message about media uploads. - * @param receiver - * @param headers - */ - void handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers); } 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 8265d48..5a75fa0 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 @@ -1,17 +1,17 @@ package com.dji.sample.media.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.model.ChannelName; +import com.dji.sample.component.mqtt.model.CommonTopicReceiver; +import com.dji.sample.component.mqtt.model.MapKeyConst; 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.component.websocket.model.BizCodeEnum; -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.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.manage.service.IDeviceService; import com.dji.sample.media.model.FileUploadCallback; import com.dji.sample.media.model.FileUploadDTO; @@ -22,14 +22,15 @@ import com.dji.sample.media.service.IMediaService; import com.dji.sample.wayline.model.dto.WaylineJobDTO; import com.dji.sample.wayline.service.IWaylineJobService; 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 java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -39,6 +40,7 @@ import java.util.stream.Collectors; * @date 2021/12/9 */ @Service +@Slf4j public class MediaServiceImpl implements IMediaService { @Autowired @@ -60,7 +62,7 @@ public class MediaServiceImpl implements IMediaService { private ISendMessageService sendMessageService; @Autowired - private IWebSocketManageService webSocketManageService; + private IDeviceRedisService deviceRedisService; @Override public Boolean fastUpload(String workspaceId, String fingerprint) { @@ -90,47 +92,44 @@ public class MediaServiceImpl implements IMediaService { } - @Override - @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK, outputChannel = ChannelName.OUTBOUND) - public void handleFileUploadCallBack(CommonTopicReceiver receiver) { + /** + * Handle media files messages reported by dock. + * @param receiver + * @return + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FILE_UPLOAD_CALLBACK, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleFileUploadCallBack(CommonTopicReceiver receiver) { FileUploadCallback callback = objectMapper.convertValue(receiver.getData(), FileUploadCallback.class); - String topic = TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + receiver.getGateway() - + TopicConst.EVENTS_SUF + TopicConst._REPLY_SUF; - CommonTopicResponse data = CommonTopicResponse.builder() - .timestamp(System.currentTimeMillis()) - .method(EventsMethodEnum.FILE_UPLOAD_CALLBACK.getMethod()) - .data(RequestsReply.success()) - .tid(receiver.getTid()) - .bid(receiver.getBid()) - .build(); - if (callback.getResult() != ResponseResult.CODE_SUCCESS) { - messageSenderService.publish(topic, data); - return; + log.error("Media file upload failed!"); + return null; } String jobId = callback.getFile().getExt().getFlightId(); + Optional deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway()); MediaFileCountDTO mediaFileCount = (MediaFileCountDTO) RedisOpsUtils.hashGet(RedisConst.MEDIA_FILE_PREFIX + receiver.getGateway(), jobId); // duplicate data - if (receiver.getBid().equals(mediaFileCount.getBid()) && receiver.getTid().equals(mediaFileCount.getTid())) { - messageSenderService.publish(topic, data); - return; + if (deviceOpt.isEmpty() + || (Objects.nonNull(mediaFileCount) && receiver.getBid().equals(mediaFileCount.getBid()) + && receiver.getTid().equals(mediaFileCount.getTid()))) { + return receiver; } - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); + + DeviceDTO device = deviceOpt.get(); Optional jobOpt = waylineJobService.getJobByJobId(device.getWorkspaceId(), jobId); if (jobOpt.isPresent()) { boolean isSave = parseMediaFile(callback, jobOpt.get()); if (!isSave) { - data.setData(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT)); + log.error("Failed to save the file to the database, please check the data manually."); + return null; } } - messageSenderService.publish(topic, data); - - notifyUploadedCount(mediaFileCount, receiver, jobId); + notifyUploadedCount(mediaFileCount, receiver, jobId, device); + return receiver; } /** @@ -139,7 +138,11 @@ public class MediaServiceImpl implements IMediaService { * @param receiver * @param jobId */ - private void notifyUploadedCount(MediaFileCountDTO mediaFileCount, CommonTopicReceiver receiver, String jobId) { + private void notifyUploadedCount(MediaFileCountDTO mediaFileCount, CommonTopicReceiver receiver, String jobId, DeviceDTO dock) { + // Do not notify when files that do not belong to the route are uploaded. + if (Objects.isNull(mediaFileCount)) { + return; + } mediaFileCount.setBid(receiver.getBid()); mediaFileCount.setTid(receiver.getTid()); mediaFileCount.setUploadedCount(mediaFileCount.getUploadedCount() + 1); @@ -163,13 +166,8 @@ public class MediaServiceImpl implements IMediaService { RedisOpsUtils.hashSet(key, jobId, mediaFileCount); } - DeviceDTO dock = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); - sendMessageService.sendBatch(webSocketManageService.getValueWithWorkspaceAndUserType(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal()), - CustomWebSocketMessage.builder() - .bizCode(BizCodeEnum.FILE_UPLOAD_CALLBACK.getCode()) - .timestamp(System.currentTimeMillis()) - .data(mediaFileCount) - .build()); + sendMessageService.sendBatch(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.FILE_UPLOAD_CALLBACK.getCode(), mediaFileCount); } private Boolean parseMediaFile(FileUploadCallback callback, WaylineJobDTO job) { @@ -184,23 +182,19 @@ public class MediaServiceImpl implements IMediaService { return fileService.saveFile(job.getWorkspaceId(), callback.getFile()) > 0; } - @Override - @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA, outputChannel = ChannelName.OUTBOUND) - public void handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers) { + /** + * Handles the highest priority message about media uploads. + * @param receiver + * @param headers + * @return + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleHighestPriorityUploadFlightTaskMedia(CommonTopicReceiver receiver, MessageHeaders headers) { Map map = objectMapper.convertValue(receiver.getData(), Map.class); if (map.isEmpty() || !map.containsKey(MapKeyConst.FLIGHT_ID)) { - return; + return null; } - messageSenderService.publish(headers.get(MqttHeaders.RECEIVED_TOPIC) + TopicConst._REPLY_SUF, - CommonTopicResponse.builder() - .data(RequestsReply.success()) - .method(receiver.getMethod()) - .timestamp(System.currentTimeMillis()) - .bid(receiver.getBid()) - .tid(receiver.getTid()) - .build()); - String dockSn = receiver.getGateway(); String jobId = map.get(MapKeyConst.FLIGHT_ID).toString(); String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + dockSn; @@ -209,7 +203,7 @@ public class MediaServiceImpl implements IMediaService { countDTO = (MediaFileCountDTO) RedisOpsUtils.get(key); if (jobId.equals(countDTO.getJobId())) { RedisOpsUtils.setWithExpire(key, countDTO, RedisConst.DEVICE_ALIVE_SECOND * 5); - return; + return null; } countDTO.setPreJobId(countDTO.getJobId()); @@ -218,13 +212,13 @@ public class MediaServiceImpl implements IMediaService { RedisOpsUtils.setWithExpire(key, countDTO, RedisConst.DEVICE_ALIVE_SECOND * 5); - DeviceDTO dock = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + dockSn); - sendMessageService.sendBatch(webSocketManageService.getValueWithWorkspaceAndUserType(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal()), - CustomWebSocketMessage.builder() - .timestamp(System.currentTimeMillis()) - .bizCode(BizCodeEnum.HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA.getCode()) - .data(countDTO) - .build()); + Optional deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway()); + if (deviceOpt.isEmpty()) { + return null; + } + sendMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA.getCode(), countDTO); + return receiver; } } 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 f24bdb2..481b6a2 100644 --- a/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java +++ b/src/main/java/com/dji/sample/wayline/controller/WaylineJobController.java @@ -5,6 +5,7 @@ import com.dji.sample.common.model.PaginationData; import com.dji.sample.common.model.ResponseResult; import com.dji.sample.wayline.model.dto.WaylineJobDTO; import com.dji.sample.wayline.model.param.CreateJobParam; +import com.dji.sample.wayline.model.param.UpdateJobParam; import com.dji.sample.wayline.service.IWaylineJobService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -86,4 +87,12 @@ public class WaylineJobController { waylineJobService.uploadMediaHighestPriority(workspaceId, jobId); return ResponseResult.success(); } + + @PutMapping("/{workspace_id}/jobs/{job_id}") + public ResponseResult updateJobStatus(@PathVariable(name = "workspace_id") String workspaceId, + @PathVariable(name = "job_id") String jobId, + @Valid @RequestBody UpdateJobParam param) { + waylineJobService.updateJobStatus(workspaceId, jobId, param); + return ResponseResult.success(); + } } diff --git a/src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java b/src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java new file mode 100644 index 0000000..2dd1ca2 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/dto/ConditionalWaylineJobKey.java @@ -0,0 +1,39 @@ +package com.dji.sample.wayline.model.dto; + +import com.dji.sample.component.redis.RedisConst; +import lombok.Data; + +import java.util.Objects; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/28 + */ +@Data +public class ConditionalWaylineJobKey { + + private String workspaceId; + + private String dockSn; + + private String jobId; + + public ConditionalWaylineJobKey(String workspaceId, String dockSn, String jobId) { + this.workspaceId = workspaceId; + this.dockSn = dockSn; + this.jobId = jobId; + } + + public ConditionalWaylineJobKey(String key) { + if (Objects.isNull(key)) { + return; + } + String[] keyArr = key.split(RedisConst.DELIMITER); + new ConditionalWaylineJobKey(keyArr[0], keyArr[1], keyArr[2]); + } + + public String getKey() { + return String.join(RedisConst.DELIMITER, workspaceId, dockSn, jobId); + } +} 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 index 64e29a2..f729961 100644 --- a/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java +++ b/src/main/java/com/dji/sample/wayline/model/dto/KmzFileProperties.java @@ -35,5 +35,5 @@ public class KmzFileProperties { public static final String TAG_PAYLOAD_SUB_ENUM_VALUE = "payloadSubEnumValue"; - public static final String TAG_TEMPLATE_ID = "templateId"; + public static final String TAG_TEMPLATE_TYPE = "templateType"; } 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 b797819..718923b 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 @@ -62,5 +62,7 @@ public class WaylineJobDTO { private Boolean uploading; + private WaylineTaskConditionDTO conditions; + private String parentId; } diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java new file mode 100644 index 0000000..6a1dc18 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskConditionDTO.java @@ -0,0 +1,22 @@ +package com.dji.sample.wayline.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/16 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WaylineTaskConditionDTO { + + private WaylineTaskReadyConditionDTO readyConditions; + + private WaylineTaskExecutableConditionDTO executableConditions; +} diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java index b920530..3297a08 100644 --- a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java +++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskCreateDTO.java @@ -30,4 +30,7 @@ public class WaylineTaskCreateDTO { private Integer outOfControlAction; + private WaylineTaskReadyConditionDTO readyConditions; + + private WaylineTaskExecutableConditionDTO executableConditions; } diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java new file mode 100644 index 0000000..24cfc4c --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskExecutableConditionDTO.java @@ -0,0 +1,23 @@ +package com.dji.sample.wayline.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/16 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class WaylineTaskExecutableConditionDTO { + + /** + * unit: MB + */ + private Integer storageCapacity; +} diff --git a/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java new file mode 100644 index 0000000..61fe5a7 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/dto/WaylineTaskReadyConditionDTO.java @@ -0,0 +1,24 @@ +package com.dji.sample.wayline.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/16 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WaylineTaskReadyConditionDTO { + + private Integer batteryCapacity; + + private Long beginTime; + + private Long endTime; +} diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java new file mode 100644 index 0000000..8643380 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineErrorCodeEnum.java @@ -0,0 +1,83 @@ +package com.dji.sample.wayline.model.enums; + +import com.dji.sample.common.error.IErrorInfo; +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Arrays; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/17 + */ +public enum WaylineErrorCodeEnum implements IErrorInfo { + + SUCCESS(0, "success", false), + + EMERGENCY_BUTTON(316026, "The emergency button at the dock was pressed.", true), + + NOT_IDLE(319001, "Task Center is not currently idle.", true), + + PERFORMING_TASK(319016, "The dock is performing other tasks.", true), + + EXPORTING_LOGS(319018, "The dock is exporting logs.", true), + + PULLING_LOGS(319019, "The dock is pulling logs.", true), + + HEIGHT_LIMIT(321513, "The wayline altitude has exceeded the height limit of the drone.", true), + + DISTANCE_LIMIT(321514, "The wayline distance has exceeded the limit of the drone.", true), + + RESTRICTED_FLIGHT_AREA(321515, "The wayline passes through a restricted flight area.", true), + + SDR_DISCONNECT(514120, "The sdr link between the dock and the drone is disconnected.", true), + + HEAVY_RAIN(514134, "Heavy rain prevented the flight.", true), + + STRONG_WIND(514135, "Strong wind prevented the flight.", true), + + POWER_DISCONNECT(514136, "The dock's power supply is disconnected.", true), + + LOW_TEMPERATURE(514137, "The low temperature of the environment prevented flight.", true), + + DEBUGGING(514145, "The dock is being debugged.", true), + + REMOTE_DEBUGGING(514146, "The dock is being debugged remotely.", true), + + DOCK_UPGRADING(514147, "The dock is being upgraded.", true), + + DOCK_WORKING(514148, "The dock is working and cannot perform new tasks.", true), + + UNKNOWN(-1, "Unknown wayline error.", false); + + private String msg; + + private int code; + + boolean block; + + WaylineErrorCodeEnum(int code, String msg, boolean block) { + this.code = code; + this.msg = msg; + this.block = block; + } + + @Override + public String getErrorMsg() { + return msg; + } + + @Override + public Integer getErrorCode() { + return code; + } + + public boolean isBlock() { + return block; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static WaylineErrorCodeEnum find(int code) { + return Arrays.stream(WaylineErrorCodeEnum.values()).filter(error -> error.code == code).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 index 4951d2c..6ad9807 100644 --- a/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineMethodEnum.java @@ -14,7 +14,11 @@ public enum WaylineMethodEnum { FLIGHT_TASK_EXECUTE("flighttask_execute"), - FLIGHT_TASK_CANCEL("flighttask_undo"); + FLIGHT_TASK_CANCEL("flighttask_undo"), + + FLIGHT_TASK_PAUSE("flighttask_pause"), + + FLIGHT_TASK_RESUME("flighttask_recovery"); private String method; diff --git a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java new file mode 100644 index 0000000..9bdf4f6 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskStatusEnum.java @@ -0,0 +1,19 @@ +package com.dji.sample.wayline.model.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/1 + */ +public enum WaylineTaskStatusEnum { + + PAUSE, RESUME; + + @JsonValue + public int getVal() { + return ordinal(); + } + +} 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 index 89e556a..db81356 100644 --- a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTaskTypeEnum.java @@ -1,22 +1,36 @@ package com.dji.sample.wayline.model.enums; -import lombok.Getter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; /** * @author sean * @version 1.3 * @date 2022/9/26 */ -@Getter public enum WaylineTaskTypeEnum { IMMEDIATE(0), - TIMED(1); + TIMED(1), + + CONDITION(2); int val; WaylineTaskTypeEnum(int val) { this.val = val; } + + @JsonValue + public int getVal() { + return val; + } + + @JsonCreator + public static WaylineTaskTypeEnum find(Integer val) { + return Arrays.stream(values()).filter(taskTypeEnum -> taskTypeEnum.val == val).findAny().get(); + } } 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 index 2388e32..760fc8d 100644 --- a/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java +++ b/src/main/java/com/dji/sample/wayline/model/enums/WaylineTemplateTypeEnum.java @@ -1,26 +1,46 @@ package com.dji.sample.wayline.model.enums; -import lombok.Getter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.Arrays; +import java.util.Optional; /** * @author sean * @version 1.3 * @date 2022/9/26 */ -@Getter public enum WaylineTemplateTypeEnum { - WAYPOINT(0), + WAYPOINT(0, "waypoint"), - MAPPING_2D(1), + MAPPING_2D(1, "mapping2d"), - MAPPING_3D(2), + MAPPING_3D(2, "mapping3d"), - MAPPING_STRIP(4); + MAPPING_STRIP(4, "mappingStrip"); int val; - WaylineTemplateTypeEnum(int val) { + String type; + + WaylineTemplateTypeEnum(int val, String type) { this.val = val; + this.type = type; + } + + @JsonValue + public int getVal() { + return val; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static WaylineTemplateTypeEnum find(Integer val) { + return Arrays.stream(values()).filter(templateTypeEnum -> templateTypeEnum.val == val).findAny().get(); + } + + public static Optional find(String type) { + return Arrays.stream(values()).filter(templateTypeEnum -> templateTypeEnum.type.equals(type)).findAny(); } } 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 f8fe651..558a9d2 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,10 +1,13 @@ package com.dji.sample.wayline.model.param; +import com.dji.sample.wayline.model.enums.WaylineTaskTypeEnum; +import com.dji.sample.wayline.model.enums.WaylineTemplateTypeEnum; import lombok.Data; import org.hibernate.validator.constraints.Range; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.util.List; /** * @author sean @@ -23,15 +26,11 @@ public class CreateJobParam { @NotBlank private String dockSn; - @Range(max = 0) @NotNull - private Integer waylineType; + private WaylineTemplateTypeEnum waylineType; - @Range(max = 1) @NotNull - private Integer taskType; - - private Long executeTime; + private WaylineTaskTypeEnum taskType; @Range(min = 20, max = 500) @NotNull @@ -40,4 +39,13 @@ public class CreateJobParam { @NotNull @Range(max = 2) private Integer outOfControlAction; + + @Range(min = 50, max = 90) + private Integer minBatteryCapacity; + + private Integer minStorageCapacity; + + private List taskDays; + + private List> taskPeriods; } diff --git a/src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java b/src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java new file mode 100644 index 0000000..dfa8e37 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/model/param/UpdateJobParam.java @@ -0,0 +1,22 @@ +package com.dji.sample.wayline.model.param; + +import com.dji.sample.wayline.model.enums.WaylineTaskStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author sean + * @version 1.3 + * @date 2023/2/1 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UpdateJobParam { + + private WaylineTaskStatusEnum status; + +} diff --git a/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java b/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java index 8bc6e63..1b0f4e2 100644 --- a/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java +++ b/src/main/java/com/dji/sample/wayline/service/IFlightTaskService.java @@ -1,8 +1,5 @@ package com.dji.sample.wayline.service; -import com.dji.sample.component.mqtt.model.CommonTopicReceiver; -import org.springframework.messaging.MessageHeaders; - /** * @author sean * @version 1.1 @@ -10,9 +7,5 @@ import org.springframework.messaging.MessageHeaders; */ public interface IFlightTaskService { - /** - * Handle the progress messages of the flight tasks reported by the dock. - * @param receiver - */ - void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers); + } 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 a18da9f..608c93f 100644 --- a/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java +++ b/src/main/java/com/dji/sample/wayline/service/IWaylineJobService.java @@ -7,6 +7,7 @@ import com.dji.sample.component.mqtt.model.CommonTopicReceiver; import com.dji.sample.wayline.model.dto.WaylineJobDTO; import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum; import com.dji.sample.wayline.model.param.CreateJobParam; +import com.dji.sample.wayline.model.param.UpdateJobParam; import org.springframework.messaging.MessageHeaders; import java.sql.SQLException; @@ -48,6 +49,14 @@ public interface IWaylineJobService { */ ResponseResult publishFlightTask(CreateJobParam param, CustomClaim customClaim) throws SQLException; + /** + * Issue wayline mission to the dock. + * @param waylineJob + * @return + * @throws SQLException + */ + ResponseResult publishOneFlightTask(WaylineJobDTO waylineJob) throws SQLException; + /** * Execute the task immediately. * @param jobId @@ -118,4 +127,19 @@ public interface IWaylineJobService { * @param jobId */ void uploadMediaHighestPriority(String workspaceId, String jobId); + + /** + * Manually control the execution status of wayline job. + * @param workspaceId + * @param jobId + * @param param + */ + void updateJobStatus(String workspaceId, String jobId, UpdateJobParam param); + + /** + * Query the wayline execution status of the dock. + * @param dockSn + * @return + */ + WaylineJobStatusEnum getWaylineState(String dockSn); } diff --git a/src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java b/src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java new file mode 100644 index 0000000..fa2b99b --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/service/IWaylineRedisService.java @@ -0,0 +1,100 @@ +package com.dji.sample.wayline.service; + +import com.dji.sample.component.mqtt.model.EventsReceiver; +import com.dji.sample.wayline.model.dto.ConditionalWaylineJobKey; +import com.dji.sample.wayline.model.dto.WaylineJobDTO; +import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver; + +import java.util.Optional; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/24 + */ +public interface IWaylineRedisService { + + /** + * Save the status of the wayline job performed by the dock into redis. + * @param dockSn + * @param data + */ + void setRunningWaylineJob(String dockSn, EventsReceiver data); + + /** + * Query the status of wayline job performed by the dock in redis. + * @param dockSn + * @return + */ + Optional> getRunningWaylineJob(String dockSn); + + /** + * Delete the wayline job status of the dock operation in redis. + * @param dockSn + * @return + */ + Boolean delRunningWaylineJob(String dockSn); + + /** + * Save the wayline job suspended by the dock to redis. + * @param dockSn + * @param jobId + */ + void setPausedWaylineJob(String dockSn, String jobId); + + /** + * Query the wayline job id suspended by the dock in redis. + * @param dockSn + * @return + */ + String getPausedWaylineJobId(String dockSn); + + /** + * Delete the wayline job suspended by the dock in redis. + * @param dockSn + * @return + */ + Boolean delPausedWaylineJob(String dockSn); + + /** + * Save the wayline job blocked by the dock to redis. + * @param dockSn + * @param jobId + */ + void setBlockedWaylineJob(String dockSn, String jobId); + + /** + * Query the wayline job id blocked by the dock in redis. + * @param dockSn + * @return + */ + String getBlockedWaylineJobId(String dockSn); + + /** + * Save the conditional wayline job by the dock to redis. + * @param waylineJob + */ + void setConditionalWaylineJob(WaylineJobDTO waylineJob); + + /** + * Query the conditional wayline job id by the dock in redis. + * @param jobId + * @return + */ + Optional getConditionalWaylineJob(String jobId); + + /** + * Delete the conditional wayline job by the dock in redis. + * @param jobId + * @return + */ + Boolean delConditionalWaylineJob(String jobId); + + Boolean addPrepareConditionalWaylineJob(WaylineJobDTO waylineJob); + + Optional getNearestConditionalWaylineJob(); + + Double getConditionalWaylineJobTime(ConditionalWaylineJobKey jobKey); + + Boolean removePrepareConditionalWaylineJob(ConditionalWaylineJobKey jobKey); +} 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 224602d..9418b94 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 @@ -1,22 +1,24 @@ package com.dji.sample.wayline.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; import com.dji.sample.component.redis.RedisConst; import com.dji.sample.component.redis.RedisOpsUtils; import com.dji.sample.component.websocket.model.BizCodeEnum; -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.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.enums.UserTypeEnum; +import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.media.model.MediaFileCountDTO; +import com.dji.sample.wayline.model.dto.ConditionalWaylineJobKey; import com.dji.sample.wayline.model.dto.WaylineJobDTO; import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver; import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum; import com.dji.sample.wayline.service.IFlightTaskService; import com.dji.sample.wayline.service.IWaylineJobService; +import com.dji.sample.wayline.service.IWaylineRedisService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -27,8 +29,10 @@ import org.springframework.integration.mqtt.support.MqttHeaders; import org.springframework.messaging.MessageHeaders; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.*; import java.util.concurrent.TimeUnit; @@ -51,14 +55,21 @@ public class FlightTaskServiceImpl implements IFlightTaskService { private ISendMessageService websocketMessageService; @Autowired - private IWebSocketManageService webSocketManageService; + private IWaylineJobService waylineJobService; @Autowired - private IWaylineJobService waylineJobService; + private IDeviceRedisService deviceRedisService; + + @Autowired + private IWaylineRedisService waylineRedisService; - @Override - @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND) - public void handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) { + /** + * Handle the progress messages of the flight tasks reported by the dock. + * @param receiver + * @param headers + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleProgress(CommonTopicReceiver receiver, MessageHeaders headers) { String receivedTopic = String.valueOf(headers.get(MqttHeaders.RECEIVED_TOPIC)); String dockSn = receivedTopic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(), receivedTopic.indexOf(TopicConst.EVENTS_SUF)); @@ -76,8 +87,7 @@ public class FlightTaskServiceImpl implements IFlightTaskService { } EventsResultStatusEnum statusEnum = EventsResultStatusEnum.find(output.getStatus()); - String key = RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn; - RedisOpsUtils.setWithExpire(key, eventsReceiver, RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); + waylineRedisService.setRunningWaylineJob(dockSn, eventsReceiver); if (statusEnum.getEnd()) { WaylineJobDTO job = WaylineJobDTO.builder() @@ -99,30 +109,66 @@ public class FlightTaskServiceImpl implements IFlightTaskService { } waylineJobService.updateJob(job); - RedisOpsUtils.del(key); - RedisOpsUtils.del(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + receiver.getBid()); + waylineRedisService.delRunningWaylineJob(dockSn); + waylineRedisService.delPausedWaylineJob(receiver.getBid()); + } + + Optional deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway()); + if (deviceOpt.isEmpty()) { + return null; + } + websocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(), + BizCodeEnum.FLIGHT_TASK_PROGRESS.getCode(), eventsReceiver); + + return receiver; + } + + /** + * Notifications will be received through this interface when tasks are ready on the device. + * @param receiver + * @param headers + */ + @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_READY, outputChannel = ChannelName.OUTBOUND_EVENTS) + public CommonTopicReceiver handleTaskNotifications(CommonTopicReceiver receiver, MessageHeaders headers) { + String receivedTopic = String.valueOf(headers.get(MqttHeaders.RECEIVED_TOPIC)); + String dockSn = receivedTopic.substring((TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT).length(), + receivedTopic.indexOf(TopicConst.EVENTS_SUF)); + List flightIds = mapper.convertValue(receiver.getData(), + new TypeReference>>(){}).get(MapKeyConst.FLIGHT_IDS); + + log.info("ready task list:{}", Arrays.toString(flightIds.toArray()) ); + // Check conditional task blocking status. + String blockedId = waylineRedisService.getBlockedWaylineJobId(dockSn); + if (!StringUtils.hasText(blockedId)) { + return null; + } + + Optional deviceOpt = deviceRedisService.getDeviceOnline(dockSn); + if (deviceOpt.isEmpty()) { + return null; } + DeviceDTO device = deviceOpt.get(); - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); - websocketMessageService.sendBatch( - webSocketManageService.getValueWithWorkspaceAndUserType( - device.getWorkspaceId(), UserTypeEnum.WEB.getVal()), - CustomWebSocketMessage.builder() - .data(eventsReceiver) - .timestamp(System.currentTimeMillis()) - .bizCode(BizCodeEnum.FLIGHT_TASK_PROGRESS.getCode()) - .build()); - - if (receiver.getNeedReply() == 1) { - messageSender.publish(receivedTopic + TopicConst._REPLY_SUF, - CommonTopicResponse.builder() - .tid(receiver.getTid()) - .bid(receiver.getBid()) - .method(EventsMethodEnum.FLIGHT_TASK_PROGRESS.getMethod()) - .timestamp(System.currentTimeMillis()) - .data(RequestsReply.success()) - .build()); + try { + for (String jobId : flightIds) { + boolean isExecute = waylineJobService.executeFlightTask(device.getWorkspaceId(), jobId); + if (!isExecute) { + return null; + } + Optional waylineJobOpt = waylineRedisService.getConditionalWaylineJob(jobId); + if (waylineJobOpt.isEmpty()) { + log.info("The conditional job has expired and will no longer be executed."); + return receiver; + } + WaylineJobDTO waylineJob = waylineJobOpt.get(); + this.retryPrepareJob(new ConditionalWaylineJobKey(device.getWorkspaceId(), dockSn, jobId), waylineJob); + return receiver; + } + } catch (Exception e) { + log.error("Failed to execute conditional task."); + e.printStackTrace(); } + return receiver; } @Scheduled(initialDelay = 10, fixedRate = 5, timeUnit = TimeUnit.SECONDS) @@ -166,4 +212,80 @@ public class FlightTaskServiceImpl implements IFlightTaskService { } } } + + @Scheduled(initialDelay = 10, fixedRate = 5, timeUnit = TimeUnit.SECONDS) + private void prepareConditionJob() { + Optional jobKeyOpt = waylineRedisService.getNearestConditionalWaylineJob(); + if (jobKeyOpt.isEmpty()) { + return; + } + ConditionalWaylineJobKey jobKey = jobKeyOpt.get(); + log.info("Check the conditional tasks of the wayline. {}", jobKey.toString()); + // format: {workspace_id}:{dock_sn}:{job_id} + double time = waylineRedisService.getConditionalWaylineJobTime(jobKey); + long now = System.currentTimeMillis(); + // prepare the task one day in advance. + int offset = 86_400_000; + + if (now + offset < time) { + return; + } + + WaylineJobDTO job = WaylineJobDTO.builder() + .jobId(jobKey.getJobId()) + .status(WaylineJobStatusEnum.FAILED.getVal()) + .executeTime(LocalDateTime.now()) + .completedTime(LocalDateTime.now()) + .code(HttpStatus.SC_INTERNAL_SERVER_ERROR).build(); + try { + Optional waylineJobOpt = waylineRedisService.getConditionalWaylineJob(jobKey.getJobId()); + if (waylineJobOpt.isEmpty()) { + job.setCode(CommonErrorEnum.REDIS_DATA_NOT_FOUND.getErrorCode()); + waylineJobService.updateJob(job); + waylineRedisService.removePrepareConditionalWaylineJob(jobKey); + return; + } + WaylineJobDTO waylineJob = waylineJobOpt.get(); + + ResponseResult result = waylineJobService.publishOneFlightTask(waylineJob); + waylineRedisService.removePrepareConditionalWaylineJob(jobKey); + + if (ResponseResult.CODE_SUCCESS == result.getCode()) { + return; + } + + // If the end time is exceeded, no more retries will be made. + waylineRedisService.delConditionalWaylineJob(jobKey.getJobId()); + if (waylineJob.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - RedisConst.WAYLINE_JOB_BLOCK_TIME * 1000 < now) { + return; + } + + // Retry if the end time has not been exceeded. + this.retryPrepareJob(jobKey, waylineJob); + + } catch (Exception e) { + log.info("Failed to prepare the conditional task."); + waylineJobService.updateJob(job); + } + + } + + private void retryPrepareJob(ConditionalWaylineJobKey jobKey, WaylineJobDTO waylineJob) { + Optional childJobOpt = waylineJobService.createWaylineJobByParent(jobKey.getWorkspaceId(), jobKey.getJobId()); + if (childJobOpt.isEmpty()) { + log.error("Failed to create wayline job."); + return; + } + + WaylineJobDTO newJob = childJobOpt.get(); + newJob.setBeginTime(LocalDateTime.now().plusSeconds(RedisConst.WAYLINE_JOB_BLOCK_TIME)); + boolean isAdd = waylineRedisService.addPrepareConditionalWaylineJob(newJob); + if (!isAdd) { + log.error("Failed to create wayline job. {}", newJob.getJobId()); + return; + } + + waylineJob.setJobId(newJob.getJobId()); + waylineRedisService.setConditionalWaylineJob(waylineJob); + } } 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 f540b66..bcea104 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 @@ -12,6 +12,7 @@ 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.enums.WaylineTemplateTypeEnum; import com.dji.sample.wayline.model.param.WaylineQueryParam; import com.dji.sample.wayline.service.IWaylineFileService; import org.dom4j.Document; @@ -187,7 +188,7 @@ public class WaylineFileServiceImpl implements IWaylineFileService { ZipEntry nextEntry = unzipFile.getNextEntry(); while (Objects.nonNull(nextEntry)) { - boolean isWaylines = (KmzFileProperties.FILE_DIR_FIRST + "/" + KmzFileProperties.FILE_DIR_SECOND_WAYLINES).equals(nextEntry.getName()); + boolean isWaylines = (KmzFileProperties.FILE_DIR_FIRST + "/" + KmzFileProperties.FILE_DIR_SECOND_TEMPLATE).equals(nextEntry.getName()); if (!isWaylines) { nextEntry = unzipFile.getNextEntry(); continue; @@ -208,11 +209,11 @@ public class WaylineFileServiceImpl implements IWaylineFileService { 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); + String templateType = document.valueOf("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_TEMPLATE_TYPE); if (!StringUtils.hasText(type) || !StringUtils.hasText(subType) || !StringUtils.hasText(payloadSubType) || !StringUtils.hasText(payloadType) || - !StringUtils.hasText(templateId)) { + !StringUtils.hasText(templateType)) { throw new RuntimeException("The file format is incorrect."); } @@ -222,7 +223,7 @@ public class WaylineFileServiceImpl implements IWaylineFileService { .objectKey(OssConfiguration.objectDirPrefix + File.separator + filename) .name(filename.substring(0, filename.lastIndexOf(WAYLINE_FILE_SUFFIX))) .sign(DigestUtils.md5DigestAsHex(file.getInputStream())) - .templateTypes(List.of(Integer.parseInt(templateId))) + .templateTypes(List.of(WaylineTemplateTypeEnum.find(templateType).map(WaylineTemplateTypeEnum::getVal).orElse(-1))) .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 acbeabe..4d4cbb2 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 @@ -12,7 +12,14 @@ 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.control.model.param.DrcModeParam; +import com.dji.sample.control.service.IDrcService; import com.dji.sample.manage.model.dto.DeviceDTO; +import com.dji.sample.manage.model.enums.DeviceModeCodeEnum; +import com.dji.sample.manage.model.enums.DockModeCodeEnum; +import com.dji.sample.manage.model.receiver.OsdDockReceiver; +import com.dji.sample.manage.model.receiver.OsdSubDeviceReceiver; +import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.manage.service.IDeviceService; import com.dji.sample.media.model.MediaFileCountDTO; import com.dji.sample.media.model.MediaMethodEnum; @@ -20,12 +27,15 @@ import com.dji.sample.media.service.IFileService; import com.dji.sample.wayline.dao.IWaylineJobMapper; import com.dji.sample.wayline.model.dto.*; import com.dji.sample.wayline.model.entity.WaylineJobEntity; +import com.dji.sample.wayline.model.enums.WaylineErrorCodeEnum; 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.model.param.UpdateJobParam; import com.dji.sample.wayline.service.IWaylineFileService; import com.dji.sample.wayline.service.IWaylineJobService; +import com.dji.sample.wayline.service.IWaylineRedisService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -37,6 +47,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import java.net.URL; import java.sql.SQLException; @@ -72,6 +83,15 @@ public class WaylineJobServiceImpl implements IWaylineJobService { @Autowired private IFileService fileService; + @Autowired + private IDrcService drcService; + + @Autowired + private IDeviceRedisService deviceRedisService; + + @Autowired + private IWaylineRedisService waylineRedisService; + private Optional insertWaylineJob(WaylineJobEntity jobEntity) { int id = mapper.insert(jobEntity); if (id <= 0) { @@ -96,8 +116,8 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .beginTime(beginTime) .endTime(endTime) .status(WaylineJobStatusEnum.PENDING.getVal()) - .taskType(param.getTaskType()) - .waylineType(param.getWaylineType()) + .taskType(param.getTaskType().getVal()) + .waylineType(param.getWaylineType().getVal()) .outOfControlAction(param.getOutOfControlAction()) .rthAltitude(param.getRthAltitude()) .mediaCount(0) @@ -123,24 +143,107 @@ public class WaylineJobServiceImpl implements IWaylineJobService { return this.insertWaylineJob(jobEntity); } + /** + * For immediate tasks, the server time shall prevail. + * @param param + */ + private void fillImmediateTime(CreateJobParam param) { + if (WaylineTaskTypeEnum.IMMEDIATE != param.getTaskType()) { + return; + } + long now = System.currentTimeMillis() / 1000; + if (CollectionUtils.isEmpty(param.getTaskDays())) { + param.setTaskDays(List.of(now)); + } + if (CollectionUtils.isEmpty(param.getTaskPeriods())) { + param.setTaskPeriods(List.of(List.of(now))); + } + } + @Override public ResponseResult publishFlightTask(CreateJobParam param, CustomClaim customClaim) throws SQLException { - if (WaylineTaskTypeEnum.IMMEDIATE.getVal() == param.getTaskType()) { - param.setExecuteTime(System.currentTimeMillis()); + fillImmediateTime(param); + + for (Long taskDay : param.getTaskDays()) { + LocalDate date = LocalDate.ofInstant(Instant.ofEpochSecond(taskDay), ZoneId.systemDefault()); + for (List taskPeriod : param.getTaskPeriods()) { + long beginTime = LocalDateTime.of(date, LocalTime.ofInstant(Instant.ofEpochSecond(taskPeriod.get(0)), ZoneId.systemDefault())) + .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + long endTime = taskPeriod.size() > 1 ? + LocalDateTime.of(date, LocalTime.ofInstant(Instant.ofEpochSecond(taskPeriod.get(1)), ZoneId.systemDefault())) + .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : beginTime; + Optional waylineJobOpt = this.createWaylineJob(param, customClaim.getWorkspaceId(), customClaim.getUsername(), beginTime, endTime); + if (waylineJobOpt.isEmpty()) { + throw new SQLException("Failed to create wayline job."); + } + + WaylineJobDTO waylineJob = waylineJobOpt.get(); + // If it is a conditional task type, add conditions to the job parameters. + addConditions(waylineJob, param, beginTime, endTime); + + return this.publishOneFlightTask(waylineJob); + } } - Optional waylineJobOpt = this.createWaylineJob(param, - customClaim.getWorkspaceId(), customClaim.getUsername(), - param.getExecuteTime(), param.getExecuteTime()); - if (waylineJobOpt.isEmpty()) { - throw new SQLException("Failed to create wayline job."); + return ResponseResult.error(); + } + + private void addConditions(WaylineJobDTO waylineJob, CreateJobParam param, Long beginTime, Long endTime) { + if (WaylineTaskTypeEnum.CONDITION != param.getTaskType()) { + return; } - WaylineJobDTO waylineJob = waylineJobOpt.get(); - boolean isOnline = deviceService.checkDeviceOnline(waylineJob.getDockSn()); + waylineJob.setConditions( + WaylineTaskConditionDTO.builder() + .executableConditions(Objects.nonNull(param.getMinStorageCapacity()) ? + WaylineTaskExecutableConditionDTO.builder().storageCapacity(param.getMinStorageCapacity()).build() : null) + .readyConditions(WaylineTaskReadyConditionDTO.builder() + .batteryCapacity(param.getMinBatteryCapacity()) + .beginTime(beginTime) + .endTime(endTime) + .build()) + .build()); + + waylineRedisService.setConditionalWaylineJob(waylineJob); + // key: wayline_job_condition, value: {workspace_id}:{dock_sn}:{job_id} + boolean isAdd = waylineRedisService.addPrepareConditionalWaylineJob(waylineJob); + if (!isAdd) { + throw new RuntimeException("Failed to create conditional job."); + } + } + + public ResponseResult publishOneFlightTask(WaylineJobDTO waylineJob) throws SQLException { + + boolean isOnline = deviceRedisService.checkDeviceOnline(waylineJob.getDockSn()); if (!isOnline) { throw new RuntimeException("Dock is offline."); } + boolean isSuccess = this.prepareFlightTask(waylineJob); + if (!isSuccess) { + return ResponseResult.error("Failed to prepare job."); + } + + // Issue an immediate task execution command. + if (WaylineTaskTypeEnum.IMMEDIATE.getVal() == waylineJob.getTaskType()) { + if (!executeFlightTask(waylineJob.getWorkspaceId(), waylineJob.getJobId())) { + return ResponseResult.error("Failed to execute job."); + } + } + + if (WaylineTaskTypeEnum.TIMED.getVal() == waylineJob.getTaskType()) { + // key: wayline_job_timed, value: {workspace_id}:{dock_sn}:{job_id} + boolean isAdd = RedisOpsUtils.zAdd(RedisConst.WAYLINE_JOB_TIMED_EXECUTE, + waylineJob.getWorkspaceId() + RedisConst.DELIMITER + waylineJob.getDockSn() + RedisConst.DELIMITER + waylineJob.getJobId(), + waylineJob.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + if (!isAdd) { + return ResponseResult.error("Failed to create scheduled job."); + } + } + + return ResponseResult.success(); + } + + private Boolean prepareFlightTask(WaylineJobDTO waylineJob) throws SQLException { // get wayline file Optional waylineFile = waylineFileService.getWaylineByWaylineId(waylineJob.getWorkspaceId(), waylineJob.getFileId()); if (waylineFile.isEmpty()) { @@ -163,17 +266,16 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .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(); + if (WaylineTaskTypeEnum.CONDITION.getVal() == waylineJob.getTaskType()) { + if (Objects.isNull(waylineJob.getConditions())) { + throw new IllegalArgumentException(); + } + flightTask.setReadyConditions(waylineJob.getConditions().getReadyConditions()); + flightTask.setExecutableConditions(waylineJob.getConditions().getExecutableConditions()); + } - ServiceReply serviceReply = messageSender.publishWithReply(topic, response); + ServiceReply serviceReply = messageSender.publishServicesTopic( + waylineJob.getDockSn(), WaylineMethodEnum.FLIGHT_TASK_PREPARE.getMethod(), flightTask, waylineJob.getJobId()); if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { log.info("Prepare task ====> Error code: {}", serviceReply.getResult()); this.updateJob(WaylineJobDTO.builder() @@ -183,26 +285,9 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .status(WaylineJobStatusEnum.FAILED.getVal()) .completedTime(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.getWorkspaceId(), waylineJob.getJobId())) { - return ResponseResult.error("Failed to execute job."); - } - } - - if (WaylineTaskTypeEnum.TIMED.getVal() == waylineJob.getTaskType()) { - boolean isAdd = RedisOpsUtils.zAdd(RedisConst.WAYLINE_JOB_TIMED_EXECUTE, - waylineJob.getWorkspaceId() + RedisConst.DELIMITER + waylineJob.getDockSn() + RedisConst.DELIMITER + waylineJob.getJobId(), - waylineJob.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); - if (!isAdd) { - return ResponseResult.error("Failed to create scheduled job."); - } + return false; } - - return ResponseResult.success(); + return true; } @Override @@ -213,7 +298,7 @@ public class WaylineJobServiceImpl implements IWaylineJobService { throw new IllegalArgumentException("Job doesn't exist."); } - boolean isOnline = deviceService.checkDeviceOnline(waylineJob.get().getDockSn()); + boolean isOnline = deviceRedisService.checkDeviceOnline(waylineJob.get().getDockSn()); if (!isOnline) { throw new RuntimeException("Dock is offline."); } @@ -221,17 +306,8 @@ public class WaylineJobServiceImpl implements IWaylineJobService { WaylineJobDTO job = waylineJob.get(); WaylineTaskCreateDTO flightTask = WaylineTaskCreateDTO.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(jobId) - .timestamp(System.currentTimeMillis()) - .data(flightTask) - .method(WaylineMethodEnum.FLIGHT_TASK_EXECUTE.getMethod()) - .build(); - - ServiceReply serviceReply = messageSender.publishWithReply(topic, response); + ServiceReply serviceReply = messageSender.publishServicesTopic( + job.getDockSn(), WaylineMethodEnum.FLIGHT_TASK_EXECUTE.getMethod(), flightTask, jobId); if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) { log.info("Execute job ====> Error code: {}", serviceReply.getResult()); this.updateJob(WaylineJobDTO.builder() @@ -240,6 +316,11 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .status(WaylineJobStatusEnum.FAILED.getVal()) .completedTime(LocalDateTime.now()) .code(serviceReply.getResult()).build()); + // The conditional task fails and enters the blocking status. + if (WaylineTaskTypeEnum.CONDITION.getVal() == job.getTaskType() + && WaylineErrorCodeEnum.find(serviceReply.getResult()).isBlock()) { + waylineRedisService.setBlockedWaylineJob(job.getDockSn(), jobId); + } return false; } @@ -248,9 +329,7 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .executeTime(LocalDateTime.now()) .status(WaylineJobStatusEnum.IN_PROGRESS.getVal()) .build()); - RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + job.getDockSn(), - EventsReceiver.builder().bid(jobId).sn(job.getDockSn()).build(), - RedisConst.DEVICE_ALIVE_SECOND * RedisConst.DEVICE_ALIVE_SECOND); + waylineRedisService.setRunningWaylineJob(job.getDockSn(), EventsReceiver.builder().bid(jobId).sn(job.getDockSn()).build()); return true; } @@ -262,8 +341,7 @@ public class WaylineJobServiceImpl implements IWaylineJobService { // Check if the task status is correct. boolean isErr = !jobIds.removeAll(waylineJobIds) || !jobIds.isEmpty() ; if (isErr) { - throw new IllegalArgumentException("These tasks have an incorrect status and cannot be canceled. " - + Arrays.toString(jobIds.toArray())); + throw new IllegalArgumentException("These tasks have an incorrect status and cannot be canceled. " + Arrays.toString(jobIds.toArray())); } // Group job id by dock sn. @@ -275,21 +353,13 @@ public class WaylineJobServiceImpl implements IWaylineJobService { } public void publishCancelTask(String workspaceId, String dockSn, List jobIds) { - boolean isOnline = deviceService.checkDeviceOnline(dockSn); + boolean isOnline = deviceRedisService.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); + ServiceReply serviceReply = messageSender.publishServicesTopic( + dockSn, WaylineMethodEnum.FLIGHT_TASK_CANCEL.getMethod(), Map.of(MapKeyConst.FLIGHT_IDS, jobIds)); 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); @@ -366,8 +436,11 @@ public class WaylineJobServiceImpl implements IWaylineJobService { String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString() + TopicConst._REPLY_SUF; - DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(RedisConst.DEVICE_ONLINE_PREFIX + receiver.getGateway()); - Optional waylineJobOpt = this.getJobByJobId(device.getWorkspaceId(), jobId); + Optional deviceOpt = deviceRedisService.getDeviceOnline(receiver.getGateway()); + if (deviceOpt.isEmpty()) { + return; + } + Optional waylineJobOpt = this.getJobByJobId(deviceOpt.get().getWorkspaceId(), jobId); if (waylineJobOpt.isEmpty()) { builder.data(RequestsReply.error(CommonErrorEnum.ILLEGAL_ARGUMENT)); messageSender.publish(topic, builder.build()); @@ -415,19 +488,12 @@ public class WaylineJobServiceImpl implements IWaylineJobService { String dockSn = jobOpt.get().getDockSn(); String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + dockSn; - if (RedisOpsUtils.checkExist(key) && - jobId.equals(((MediaFileCountDTO) RedisOpsUtils.get(key)).getJobId())) { + if (RedisOpsUtils.checkExist(key) && jobId.equals(((MediaFileCountDTO) RedisOpsUtils.get(key)).getJobId())) { return; } - ServiceReply reply = messageSender.publishWithReply(TopicConst.THING_MODEL_PRE + TopicConst.PRODUCT + dockSn + TopicConst.SERVICES_SUF, - CommonTopicResponse.builder() - .tid(UUID.randomUUID().toString()) - .bid(UUID.randomUUID().toString()) - .timestamp(System.currentTimeMillis()) - .method(MediaMethodEnum.UPLOAD_FLIGHT_TASK_MEDIA_PRIORITIZE.getMethod()) - .data(Map.of(MapKeyConst.FLIGHT_ID, jobId)) - .build()); + ServiceReply reply = messageSender.publishServicesTopic( + dockSn, MediaMethodEnum.UPLOAD_FLIGHT_TASK_MEDIA_PRIORITIZE.getMethod(), Map.of(MapKeyConst.FLIGHT_ID, jobId)); if (ResponseResult.CODE_SUCCESS != reply.getResult()) { throw new RuntimeException("Failed to set media job upload priority. Error Code: " + reply.getResult()); } @@ -467,6 +533,91 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .build(); } + @Override + public void updateJobStatus(String workspaceId, String jobId, UpdateJobParam param) { + Optional waylineJobOpt = this.getJobByJobId(workspaceId, jobId); + if (waylineJobOpt.isEmpty()) { + throw new RuntimeException("The job does not exist."); + } + WaylineJobDTO waylineJob = waylineJobOpt.get(); + WaylineJobStatusEnum statusEnum = this.getWaylineState(waylineJob.getDockSn()); + if (statusEnum.getEnd() || WaylineJobStatusEnum.PENDING == statusEnum) { + throw new RuntimeException("The wayline job status does not match, and the operation cannot be performed."); + } + + switch (param.getStatus()) { + case PAUSE: + pauseJob(workspaceId, waylineJob.getDockSn(), jobId, statusEnum); + break; + case RESUME: + resumeJob(workspaceId, waylineJob.getDockSn(), jobId, statusEnum); + break; + } + + } + + public WaylineJobStatusEnum getWaylineState(String dockSn) { + Optional dockOpt = deviceRedisService.getDeviceOnline(dockSn); + if (dockOpt.isEmpty() || !StringUtils.hasText(dockOpt.get().getChildDeviceSn())) { + return WaylineJobStatusEnum.UNKNOWN; + } + Optional dockOsdOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDockReceiver.class); + Optional deviceOsdOpt = deviceRedisService.getDeviceOsd(dockOpt.get().getChildDeviceSn(), OsdSubDeviceReceiver.class); + if (dockOsdOpt.isEmpty() || deviceOsdOpt.isEmpty() || DockModeCodeEnum.WORKING != dockOsdOpt.get().getModeCode()) { + return WaylineJobStatusEnum.UNKNOWN; + } + + OsdSubDeviceReceiver osdDevice = deviceOsdOpt.get(); + if (DeviceModeCodeEnum.WAYLINE == osdDevice.getModeCode() + || DeviceModeCodeEnum.MANUAL == osdDevice.getModeCode() + || DeviceModeCodeEnum.TAKEOFF_AUTO == osdDevice.getModeCode()) { + if (StringUtils.hasText(waylineRedisService.getPausedWaylineJobId(dockSn))) { + return WaylineJobStatusEnum.PAUSED; + } + if (waylineRedisService.getRunningWaylineJob(dockSn).isPresent()) { + return WaylineJobStatusEnum.IN_PROGRESS; + } + } + return WaylineJobStatusEnum.UNKNOWN; + } + + private void pauseJob(String workspaceId, String dockSn, String jobId, WaylineJobStatusEnum statusEnum) { + if (WaylineJobStatusEnum.PAUSED == statusEnum && jobId.equals(waylineRedisService.getPausedWaylineJobId(dockSn))) { + waylineRedisService.setPausedWaylineJob(dockSn, jobId); + return; + } + + ServiceReply reply = messageSender.publishServicesTopic( + dockSn, WaylineMethodEnum.FLIGHT_TASK_PAUSE.getMethod(), "", jobId); + if (ResponseResult.CODE_SUCCESS != reply.getResult()) { + throw new RuntimeException("Failed to pause wayline job. Error Code: " + reply.getResult()); + } + waylineRedisService.delRunningWaylineJob(dockSn); + waylineRedisService.setPausedWaylineJob(dockSn, jobId); + } + + private void resumeJob(String workspaceId, String dockSn, String jobId, WaylineJobStatusEnum statusEnum) { + Optional> runningDataOpt = waylineRedisService.getRunningWaylineJob(dockSn); + if (WaylineJobStatusEnum.IN_PROGRESS == statusEnum && jobId.equals(runningDataOpt.map(EventsReceiver::getSn).get())) { + waylineRedisService.setRunningWaylineJob(dockSn, runningDataOpt.get()); + return; + } + ServiceReply reply = messageSender.publishServicesTopic( + dockSn, WaylineMethodEnum.FLIGHT_TASK_RESUME.getMethod(), "", jobId); + if (ResponseResult.CODE_SUCCESS != reply.getResult()) { + throw new RuntimeException("Failed to resume wayline job. Error Code: " + reply.getResult()); + } + + runningDataOpt.ifPresent(runningData -> waylineRedisService.setRunningWaylineJob(dockSn, runningData)); + waylineRedisService.delPausedWaylineJob(dockSn); + + if (deviceService.checkDockDrcMode(dockSn)) { + drcService.deviceDrcExit(workspaceId, DrcModeParam.builder().dockSn(dockSn) + .clientId(drcService.getDrcModeInRedis(dockSn)).build()); + } + + } + private WaylineJobDTO entity2Dto(WaylineJobEntity entity) { if (entity == null) { return null; @@ -484,8 +635,8 @@ public class WaylineJobServiceImpl implements IWaylineJobService { .username(entity.getUsername()) .workspaceId(entity.getWorkspaceId()) .status(WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() && - RedisOpsUtils.checkExist(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + entity.getJobId()) ? - WaylineJobStatusEnum.PAUSED.getVal() : entity.getStatus()) + entity.getJobId().equals(waylineRedisService.getPausedWaylineJobId(entity.getDockSn())) ? + WaylineJobStatusEnum.PAUSED.getVal() : entity.getStatus()) .code(entity.getErrorCode()) .beginTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBeginTime()), ZoneId.systemDefault())) .endTime(Objects.nonNull(entity.getEndTime()) ? @@ -503,11 +654,12 @@ public class WaylineJobServiceImpl implements IWaylineJobService { if (Objects.nonNull(entity.getEndTime())) { builder.endTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault())); } - if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() && RedisOpsUtils.getExpire(entity.getJobId()) > 0) { - EventsReceiver taskProgress = (EventsReceiver) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + entity.getDockSn()); - if (Objects.nonNull(taskProgress.getOutput()) && Objects.nonNull(taskProgress.getOutput().getProgress())) { - builder.progress(taskProgress.getOutput().getProgress().getPercent()); - } + if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus()) { + builder.progress(waylineRedisService.getRunningWaylineJob(entity.getDockSn()) + .map(EventsReceiver::getOutput) + .map(WaylineTaskProgressReceiver::getProgress) + .map(WaylineTaskProgress::getPercent) + .orElse(null)); } if (entity.getMediaCount() == 0) { diff --git a/src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java b/src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java new file mode 100644 index 0000000..b93e155 --- /dev/null +++ b/src/main/java/com/dji/sample/wayline/service/impl/WaylineRedisServiceImpl.java @@ -0,0 +1,112 @@ +package com.dji.sample.wayline.service.impl; + +import com.dji.sample.component.mqtt.model.EventsReceiver; +import com.dji.sample.component.redis.RedisConst; +import com.dji.sample.component.redis.RedisOpsUtils; +import com.dji.sample.wayline.model.dto.ConditionalWaylineJobKey; +import com.dji.sample.wayline.model.dto.WaylineJobDTO; +import com.dji.sample.wayline.model.dto.WaylineTaskProgressReceiver; +import com.dji.sample.wayline.service.IWaylineRedisService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Objects; +import java.util.Optional; + +/** + * @author sean + * @version 1.4 + * @date 2023/3/24 + */ +@Service +public class WaylineRedisServiceImpl implements IWaylineRedisService { + + @Override + public void setRunningWaylineJob(String dockSn, EventsReceiver data) { + RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn, data, RedisConst.DRC_MODE_ALIVE_SECOND); + } + + @Override + public Optional> getRunningWaylineJob(String dockSn) { + return Optional.ofNullable((EventsReceiver) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn)); + } + + @Override + public Boolean delRunningWaylineJob(String dockSn) { + return RedisOpsUtils.del(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + dockSn); + } + + @Override + public void setPausedWaylineJob(String dockSn, String jobId) { + RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + dockSn, jobId, RedisConst.DRC_MODE_ALIVE_SECOND); + } + + @Override + public String getPausedWaylineJobId(String dockSn) { + return (String) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + dockSn); + } + + @Override + public Boolean delPausedWaylineJob(String dockSn) { + return RedisOpsUtils.del(RedisConst.WAYLINE_JOB_PAUSED_PREFIX + dockSn); + } + + @Override + public void setBlockedWaylineJob(String dockSn, String jobId) { + RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_BLOCK_PREFIX + dockSn, jobId, RedisConst.WAYLINE_JOB_BLOCK_TIME); + } + + @Override + public String getBlockedWaylineJobId(String dockSn) { + return (String) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_BLOCK_PREFIX + dockSn); + } + + @Override + public void setConditionalWaylineJob(WaylineJobDTO waylineJob) { + if (!StringUtils.hasText(waylineJob.getJobId())) { + throw new RuntimeException("Job id can't be null."); + } + RedisOpsUtils.setWithExpire(RedisConst.WAYLINE_JOB_CONDITION_PREFIX + waylineJob.getJobId(), waylineJob, + (Duration.between(waylineJob.getEndTime(), LocalDateTime.now()).getSeconds())); + } + + @Override + public Optional getConditionalWaylineJob(String jobId) { + return Optional.ofNullable((WaylineJobDTO) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_CONDITION_PREFIX + jobId)); + } + + @Override + public Boolean delConditionalWaylineJob(String jobId) { + return RedisOpsUtils.del(RedisConst.WAYLINE_JOB_CONDITION_PREFIX + jobId); + } + + @Override + public Boolean addPrepareConditionalWaylineJob(WaylineJobDTO waylineJob) { + if (Objects.isNull(waylineJob.getBeginTime())) { + return false; + } + // value: {workspace_id}:{dock_sn}:{job_id} + return RedisOpsUtils.zAdd(RedisConst.WAYLINE_JOB_CONDITION_PREPARE, + waylineJob.getWorkspaceId() + RedisConst.DELIMITER + waylineJob.getDockSn() + RedisConst.DELIMITER + waylineJob.getJobId(), + waylineJob.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); + } + + @Override + public Optional getNearestConditionalWaylineJob() { + return Optional.ofNullable(RedisOpsUtils.zGetMin(RedisConst.WAYLINE_JOB_CONDITION_PREPARE)) + .map(Object::toString).map(ConditionalWaylineJobKey::new); + } + + @Override + public Double getConditionalWaylineJobTime(ConditionalWaylineJobKey jobKey) { + return RedisOpsUtils.zScore(RedisConst.WAYLINE_JOB_CONDITION_PREPARE, jobKey.getKey()); + } + + @Override + public Boolean removePrepareConditionalWaylineJob(ConditionalWaylineJobKey jobKey) { + return RedisOpsUtils.zRemove(RedisConst.WAYLINE_JOB_CONDITION_PREPARE, jobKey.getKey()); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 75c4872..a5c937a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -52,6 +52,11 @@ mqtt: path: # Topics that need to be subscribed when initially connecting to mqtt, multiple topics are divided by ",". inbound-topic: sys/product/+/status,thing/product/+/requests + DRC: + protocol: WS # @see com.dji.sample.component.mqtt.model.MqttProtocolEnum + host: Please enter your ip. + port: 8083 + path: /mqtt url: manage: @@ -75,7 +80,7 @@ url: # Tutorial: https://www.alibabacloud.com/help/en/object-storage-service/latest/use-a-temporary-credential-provided-by-sts-to-access-oss oss: - enable: true + enable: false provider: ali # @see com.dji.sample.component.OssConfiguration.model.enums.OssTypeEnum endpoint: https://oss-cn-hangzhou.aliyuncs.com access-key: Please enter your access key.