Procházet zdrojové kódy

cctv control http request add

shjung před 3 roky
rodič
revize
d34db88e94

+ 10 - 3
src/main/java/com/its/api/its/controller/cctv/CctvCommonController.java

@@ -23,15 +23,22 @@ public class CctvCommonController {
     private final TbCctvCtlrService service;
     private final CctvControlService controlService;
 
-    @ApiOperation(value = "CCTV 목록 조회(TB_CCTV_CTLR)", response = TbCctvCtlrDto.class, responseContainer = "ArrayList")
+    @ApiOperation(value = "CCTV 목록 조회-삭제되지 않은(TB_CCTV_CTLR)", response = TbCctvCtlrDto.class, responseContainer = "ArrayList")
     @GetMapping(value = "/cctv-list", produces = {"application/json; charset=utf8"})
     public List<TbCctvCtlrDto> findAllCctvList() {
         return this.service.findAllList();
     }
 
-    @ApiOperation(value = "CCTV PTZ 제어", response = CctvControlDto.CctvControlPtzRes.class)
+    @ApiOperation(value = "CCTV PTZ 제어", response = CctvControlDto.CctvControlRes.class)
     @PostMapping(value = "/control/ptz/{id}", produces = {"application/json; charset=utf8"})
-    public CctvControlDto.CctvControlPtzRes controlPtz(@PathVariable("id") Long id, @RequestBody @Valid final CctvControlDto.CctvControlPtzReq req) {
+    public CctvControlDto.CctvControlRes controlPtz(@PathVariable("id") Long id, @RequestBody @Valid final CctvControlDto.CctvControlPtzReq req) {
         return this.controlService.controlPtz(id, req);
     }
+
+    @ApiOperation(value = "CCTV Preset 제어", response = CctvControlDto.CctvControlRes.class)
+    @PostMapping(value = "/control/preset/{id}", produces = {"application/json; charset=utf8"})
+    public CctvControlDto.CctvControlRes controlPreset(@PathVariable("id") Long id, @RequestBody @Valid final CctvControlDto.CctvControlPresetReq req) {
+        return this.controlService.controlPreset(id, req);
+    }
+
 }

+ 10 - 10
src/main/java/com/its/api/its/controller/cctv/TbCctvPsetController.java

@@ -27,10 +27,10 @@ public class TbCctvPsetController {
         return service.findAll();
     }
 
-    @ApiOperation(value = "CCTV 프리셋 개별조회(TB_CCTV_PSET)", response = TbCctvPsetDto.class)
+    @ApiOperation(value = "CCTV 프리셋 개별조회(TB_CCTV_PSET)", response = TbCctvPsetDto.class, responseContainer = "ArrayList")
     @GetMapping(value = "/{id}", produces = {"application/json; charset=utf8"})
-    public TbCctvPsetDto findById(@PathVariable final TbCctvPsetKey id) {
-        return this.service.findById(id);
+    public List<TbCctvPsetDto> findListById(@PathVariable final Long id) {
+        return this.service.findListById(id);
     }
 
     @ApiOperation(value = "CCTV 프리셋 정보변경(TB_CCTV_PSET)", response = TbCctvPsetDto.class)
@@ -39,7 +39,7 @@ public class TbCctvPsetController {
         return this.service.updateById(id, req);
     }
 
-    @ApiOperation(value = "CCTV 프리셋 정보변경/생성-목록(TB_CCTV_PSET)", response = TbCctvPsetDto.class, responseContainer = "ArrayList")
+    /*@ApiOperation(value = "CCTV 프리셋 정보변경/생성-목록(TB_CCTV_PSET)", response = TbCctvPsetDto.class, responseContainer = "ArrayList")
     @PostMapping(value = "", produces = {"application/json; charset=utf8"})
     public List<TbCctvPsetDto> mergeInfoList(@RequestBody @Valid final List<TbCctvPsetDto.TbCctvPsetUpdReq> listReq) {
         return this.service.mergeInfoList(listReq);
@@ -49,18 +49,18 @@ public class TbCctvPsetController {
     @PostMapping(value = "/{id}", produces = {"application/json; charset=utf8"})
     public TbCctvPsetDto mergeInfo(@PathVariable("id") TbCctvPsetKey id, @RequestBody @Valid final TbCctvPsetDto.TbCctvPsetUpdReq req) {
         return this.service.mergeInfo(req);
-    }
+    }*/
 
     @ApiOperation(value = "CCTV 프리셋 정보삭제-개별(TB_CCTV_PSET)", response = TbCctvPsetDto.class)
-    @DeleteMapping(value = "/{id}", produces = {"application/json; charset=utf8"})
-    public TbCctvPsetDto deleteDataById(@PathVariable("id") TbCctvPsetKey id) {
-        return this.service.deleteById(id);
+    @DeleteMapping(value = "/{id}/{psetnmbr}", produces = {"application/json; charset=utf8"})
+    public TbCctvPsetDto deleteDataById(@PathVariable("id") Long id, @PathVariable("psetnmbr") String psetnmbr) {
+        return this.service.deleteById(id, psetnmbr);
     }
 
-    @ApiOperation(value = "CCTV 프리셋 정보삭제-목록(TB_CCTV_PSET)", response = TbCctvPsetDto.class, responseContainer = "ArrayList")
+    /*@ApiOperation(value = "CCTV 프리셋 정보삭제-목록(TB_CCTV_PSET)", response = TbCctvPsetDto.class, responseContainer = "ArrayList")
     @DeleteMapping(value = "", produces = {"application/json; charset=utf8"})
     public List<TbCctvPsetDto> deleteDataByIds(@RequestBody @Valid final List<TbCctvPsetKey> ids) {
         return this.service.deleteByIds(ids);
-    }
+    }*/
 
 }

+ 61 - 10
src/main/java/com/its/api/its/model/dto/cctv/CctvControlDto.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.*;
 
+import javax.validation.constraints.Positive;
 import javax.validation.constraints.PositiveOrZero;
 import java.io.Serializable;
 
@@ -32,7 +33,7 @@ public class CctvControlDto implements Serializable {
         @JsonProperty("action")
         private String action;
 
-        @ApiModelProperty("CCTV 제어기 ID, Nullable = Y, VARCHAR(30)")  // Y VARCHAR(30)
+        @ApiModelProperty("제어속도(0~63)")
         @JsonProperty("speed")
         @PositiveOrZero
         private Integer speed;
@@ -49,34 +50,84 @@ public class CctvControlDto implements Serializable {
             this.userId = user_id;
         }
     }
-    @ApiModel("CctvControlPtzRes(CCTV PTZ 제어 응답)")
+    @ApiModel("CctvControlPresetReq(CCTV PTZ 제어 정보)")
     @Getter
     @Setter
     @ToString
     @NoArgsConstructor(access = AccessLevel.PROTECTED)
-    public static class CctvControlPtzRes {
+    public static class CctvControlPresetReq {
 
-        @ApiModelProperty("제어 명령(tilt-up, tilt-down, pan-left, pan-right, zoom-in, zoom-out, focus-in, focus-out, up-left, up-right, down-left, down-right)")
+        @ApiModelProperty("제어 명령(preset-default)")
         @JsonProperty("command")
         private String command;
 
-        @ApiModelProperty("제어 명령 제어(start, stop)")
+        @ApiModelProperty("제어 명령 제어(call, save, delete)")
+        @JsonProperty("action")
+        private String action;
+
+        @ApiModelProperty("프리셋 번호(1~255)")
+        @JsonProperty("no")
+        @Positive
+        private Integer no;
+
+        @ApiModelProperty("속도(0~63), save only")
+        @JsonProperty("speed")
+        @PositiveOrZero
+        private Integer speed;
+
+        @ApiModelProperty("프리셋 타임(0~60 sec), save only")
+        @JsonProperty("time")
+        @PositiveOrZero
+        private Integer time;
+
+        @ApiModelProperty("프리셋 명(영문명만, 20 byte) save only, not use.")
+        @JsonProperty("name")
+        private String name;
+
+        @ApiModelProperty("로그인 사용자 ID")
+        @JsonProperty("user_id")
+        private String userId;
+
+        @Builder
+        public CctvControlPresetReq(String command, String action, Integer speed, String user_id) {
+            this.command = command;
+            this.action = action;
+            this.speed = speed;
+            this.userId = user_id;
+        }
+    }
+    @ApiModel("CctvControlRes(CCTV Control 응답)")
+    @Getter
+    @Setter
+    @ToString
+    @NoArgsConstructor(access = AccessLevel.PROTECTED)
+    public static class CctvControlRes {
+
+        @ApiModelProperty("제어 명령(tilt-up, tilt-down, pan-left, pan-right, zoom-in, zoom-out, focus-in, focus-out, up-left, up-right, down-left, down-right), 프리셋제어(call, save, delete)")
+        @JsonProperty("command")
+        private String command;
+
+        @ApiModelProperty("제어 명령 제어(start, stop), 프리셋제어(call, save, delete)")
         @JsonProperty("action")
         private String action;
 
-        @ApiModelProperty("제어결과")
-        @JsonProperty("result")
-        private Integer result;
+        @ApiModelProperty("제어결과(0: 성공, 기타: 오류")
+        @JsonProperty("error")
+        private Integer error;
 
         @ApiModelProperty("제어결과메시지")
         @JsonProperty("message")
         private String message;
 
+        public void setResult(Integer error, String message) {
+            this.error = error;
+            this.message = message;
+        }
         @Builder
-        public CctvControlPtzRes(String command, String action, Integer result, String message) {
+        public CctvControlRes(String command, String action, Integer error, String message) {
             this.command = command;
             this.action = action;
-            this.result = result;
+            this.error = error;
             this.message = message;
         }
     }

+ 6 - 0
src/main/java/com/its/api/its/repository/cctv/TbCctvPsetRepository.java

@@ -4,9 +4,15 @@ import com.its.api.its.model.entity.cctv.TbCctvPset;
 import com.its.api.its.model.entity.cctv.TbCctvPsetKey;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 @Repository
 public interface TbCctvPsetRepository extends JpaRepository<TbCctvPset, TbCctvPsetKey>, JpaSpecificationExecutor<TbCctvPset> {
 
+    @Query("select p from TbCctvPset p where p.cctvMngmNmbr = :nmbr")
+    List<TbCctvPset> findListById(@Param("nmbr") Long nmbr);
 }

+ 119 - 56
src/main/java/com/its/api/its/service/cctv/CctvControlService.java

@@ -3,15 +3,18 @@ package com.its.api.its.service.cctv;
 import com.its.api.its.model.dto.cctv.CctvControlDto;
 import com.its.api.its.model.entity.cctv.TbCctvCtlr;
 import com.its.api.its.repository.cctv.TbCctvCtlrRepository;
-import com.its.api.utils.SysUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.stereotype.Service;
 
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 import java.util.NoSuchElementException;
 
 @Slf4j
@@ -30,85 +33,124 @@ public class CctvControlService {
                 .orElseThrow(() -> new NoSuchElementException("데이터가 존재하지 않습니다: " + id));
     }
 
-    public CctvControlDto.CctvControlPtzRes controlPtzProbeDigital(String ipAddr, String userId, String userPswd, CctvControlDto.CctvControlPtzReq req) {
-        CctvControlDto.CctvControlPtzRes result = new CctvControlDto.CctvControlPtzRes(req.getCommand(), req.getAction(), 0, "success");
+    public CctvControlDto.CctvControlRes controlPresetProbeDigital(String ipAddr, String userId, String userPswd, CctvControlDto.CctvControlPresetReq req) {
+        CctvControlDto.CctvControlRes result = new CctvControlDto.CctvControlRes(req.getCommand(), req.getAction(), 0, "success");
+        return result;
+    }
+    public CctvControlDto.CctvControlRes controlPtzProbeDigital(String ipAddr, String userId, String userPswd, CctvControlDto.CctvControlPtzReq req) {
+        CctvControlDto.CctvControlRes result = new CctvControlDto.CctvControlRes(req.getCommand(), req.getAction(), 0, "success");
         return result;
     }
 
-    public static class ZenoCommand {
-        private byte[] cmd;
-        public ZenoCommand(byte[] cmd) {
-            this.cmd = cmd;
-        }
-        public byte[] getCmd() {
-            if (this.cmd == null) {
-                return null;
-            }
-            byte cmd[] = new byte[this.cmd.length];
-            System.arraycopy(this.cmd, 0, cmd, 0, this.cmd.length);
-            return cmd;
+    /**
+     * ZENO Preset control
+     * @param ipAddr
+     * @param userId
+     * @param userPswd
+     * @param req
+     * @return
+     */
+    public CctvControlDto.CctvControlRes controlPresetZeno(String ipAddr, String userId, String userPswd, CctvControlDto.CctvControlPresetReq req) {
+        CctvControlDto.CctvControlRes result = new CctvControlDto.CctvControlRes(req.getCommand(), req.getAction(), 0, "success");
+        byte[] controlData = this.zenoControl.getPresetCmd(req.getAction(), req.getNo(), req.getSpeed(), req.getTime(), req.getName());
+        if (controlData == null) {
+            result.setResult(9, "unknown command");
+            return result;
         }
+        return requestHttpPostZeno(result, controlData, ipAddr, userId, userPswd);
     }
 
-    public byte[] getZenoCommand(CctvControlDto.CctvControlPtzReq req) {
-        return this.zenoControl.getCmd(req.getCommand(), req.getAction(), req.getSpeed());
+    /**
+     * ZENO PTZ control
+     * @param ipAddr
+     * @param userId
+     * @param userPswd
+     * @param req
+     * @return
+     */
+    public CctvControlDto.CctvControlRes controlPtzZeno(String ipAddr, String userId, String userPswd, CctvControlDto.CctvControlPtzReq req) {
+        CctvControlDto.CctvControlRes result = new CctvControlDto.CctvControlRes(req.getCommand(), req.getAction(), 0, "success");
+        byte[] controlData = this.zenoControl.getPtzCmd(req.getCommand(), req.getAction(), req.getSpeed());
+        if (controlData == null) {
+            result.setResult(9, "unknown command");
+            return result;
+        }
+        return requestHttpPostZeno(result, controlData, ipAddr, userId, userPswd);
     }
-    public CctvControlDto.CctvControlPtzRes controlPtzZeno(String ipAddr, String userId, String userPswd, CctvControlDto.CctvControlPtzReq req) {
-        CctvControlDto.CctvControlPtzRes result = new CctvControlDto.CctvControlPtzRes(req.getCommand(), req.getAction(), 0, "success");
 
+    /**
+     * ZENO Request HTTP Post
+     * @param result
+     * @param controlData
+     * @param ipAddr
+     * @param userId
+     * @param userPswd
+     * @return
+     */
+    public CctvControlDto.CctvControlRes requestHttpPostZeno(CctvControlDto.CctvControlRes result, byte[] controlData, String ipAddr, String userId, String userPswd) {
+        String encoding = Base64.getEncoder().encodeToString((userId + ":" + userPswd).getBytes());
         String apiUrl = "http://" + ipAddr + "/cgi-bin/admin/uartctrl.cgi";
-        byte[] controlData = getZenoCommand(req);
-        log.error("{}, {}", apiUrl, SysUtils.byteArrayToHex(controlData));
+        //log.info("{}, {}", apiUrl, SysUtils.byteArrayToHex(controlData));
+
+        HttpURLConnection connection = null;
         try {
+            // POST /cgi-bin/admin/uartctrl.cgi HTTP/1.1
+            // User-Agent: Mozilla/3.0 (compatible; Indy Library)
+            // Accept: text/html, */*
+            // Indy 로 할경우
+            // [POST /cgi-bin/admin/uartctrl.cgi HTTP/1.1
+            // User-Agent: Java/1.8.0_25
+            // Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
             URL url = new URL(apiUrl);
-            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection = (HttpURLConnection)url.openConnection();
             connection.setConnectTimeout(5000); //서버에 연결되는 Timeout 시간 설정
             connection.setReadTimeout(5000); // InputStream 읽어 오는 Timeout 시간 설정
-            //connection.addRequestProperty("x-api-key", RestTestCommon.API_KEY); //key값 설정
 
-            connection.setDoOutput(true); //POST 데이터를 OutputStream으로 넘겨 주겠다는 설정
-            connection.setRequestMethod("POST");
-
-            //json 으로 message 를 전달하고자 할 때
-            connection.setRequestProperty("Content-Type", "application/json");
+            connection.setDoOutput(true); //POST 데이터를 OutputStream 으로 넘겨 주겠다는 설정
             connection.setDoInput(true);
-
-            connection.setUseCaches(false);
-            connection.setDefaultUseCaches(false);
-
-            PrintWriter writer = null;
-            OutputStream output = connection.getOutputStream();
-            writer = new PrintWriter(new OutputStreamWriter(output, "utf-8"), true);
-
-
-            // Send binary file.
-            /*writer.append("--" + boundary).append("\r\n");
-            writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append("\r\n");
-            writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName()).append("\r\n");
-            writer.append("Content-Transfer-Encoding: binary").append("\r\n");
-            writer.append("\r\n").flush();
-*/
-            OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("Content-Type", "application/octet-stream");
+            connection.setRequestProperty("Accept", "text/html, */*");
+            connection.setRequestProperty("Accept-Encoding", "identity");
+            connection.setRequestProperty("Authorization", "Basic " + encoding);
+            //connection.setRequestProperty("charset", "UTF-8");
+            //connection.setRequestProperty("Content-Length", Integer.toString(controlData.length));
+            connection.setRequestProperty("User-Agent", "Mozilla/3.0");
+
+            //connection.setUseCaches(false);
+            //connection.setDefaultUseCaches(false);
+
+            DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
+            wr.write(controlData);
             wr.flush();
+            wr.close();
 
-            StringBuilder sb = new StringBuilder();
             if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
-                //Stream을 처리해줘야 하는 귀찮음이 있음.
-                BufferedReader br = new BufferedReader(
-                        new InputStreamReader(connection.getInputStream(), "utf-8"));
                 String line;
+                StringBuilder sb = new StringBuilder();
+                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
                 while ((line = br.readLine()) != null) {
                     sb.append(line).append("\n");
                 }
                 br.close();
-                System.out.println("" + sb.toString());
+                log.info("HTTP_OK: CCTV, {}, {}", ipAddr, sb);
+                result.setResult(0, sb.toString());
             } else {
-                System.out.println(connection.getResponseMessage());
+                log.error("HTTP_FAIL: {}", connection.getResponseMessage());
+                if (connection.getResponseMessage() == null) {
+                    result.setResult(2, "cctv network fail.");
+                } else {
+                    result.setResult(3, connection.getResponseMessage());
+                }
             }
         } catch (Exception e){
-            log.error("{}", e);
+            log.error("Exception: {}", e.getMessage());
+            result.setResult(4, e.getMessage());
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
         }
-
         return result;
     }
 
@@ -118,7 +160,7 @@ public class CctvControlService {
      * @param req
      * @return
      */
-    public CctvControlDto.CctvControlPtzRes controlPtz(Long id, CctvControlDto.CctvControlPtzReq req) {
+    public CctvControlDto.CctvControlRes controlPtz(Long id, CctvControlDto.CctvControlPtzReq req) {
         log.info("{}", req);
         TbCctvCtlr cctv = this.requireOne(id);
         if (StringUtils.equals("1", cctv.getCctvType())) {
@@ -129,6 +171,27 @@ public class CctvControlService {
             // 프로브디지털
             return this.controlPtzProbeDigital(cctv.getCctvCtlrIp(), "admin", "1234", req);
         }
-        return new CctvControlDto.CctvControlPtzRes(req.getCommand(), req.getAction(), 1, "unknown cctv type");
+        return new CctvControlDto.CctvControlRes(req.getCommand(), req.getAction(), 1, "unknown cctv type");
     }
+
+    /**
+     * CCTV Preset 컨트롤
+     * @param id
+     * @param req
+     * @return
+     */
+    public CctvControlDto.CctvControlRes controlPreset(Long id, CctvControlDto.CctvControlPresetReq req) {
+        log.info("{}", req);
+        TbCctvCtlr cctv = this.requireOne(id);
+        if (StringUtils.equals("1", cctv.getCctvType())) {
+            // 제노 CCTV
+            return this.controlPresetZeno(cctv.getCctvCtlrIp(), "root", "pass", req);
+        }
+        else if (StringUtils.equals("2", cctv.getCctvType())) {
+            // 프로브디지털
+            return this.controlPresetProbeDigital(cctv.getCctvCtlrIp(), "admin", "1234", req);
+        }
+        return new CctvControlDto.CctvControlRes(req.getCommand(), req.getAction(), 1, "unknown cctv type");
+    }
+
 }

+ 30 - 3
src/main/java/com/its/api/its/service/cctv/CctvControlZenoService.java

@@ -16,6 +16,7 @@ public class CctvControlZenoService {
 
     Map<String, ZenoCommand> startCmd = new HashMap<>();
     Map<String, ZenoCommand> stopCmd = new HashMap<>();
+    Map<String, ZenoCommand> presetCmd = new HashMap<>();
 
     @PostConstruct
     void init() {
@@ -36,9 +37,15 @@ public class CctvControlZenoService {
         this.stopCmd.put("zoom-out",  new ZenoCommand(new byte[]{ 0x50, (byte)0xFF, 0x01, 0x00, 0x00, 0x00, (byte)0xFF, 0x01 }));
         this.stopCmd.put("focus-in",  new ZenoCommand(new byte[]{ 0x50, (byte)0xFF, 0x01, 0x00, 0x08, 0x00, (byte)0xFF, 0x31 }));
         this.stopCmd.put("focus-out", new ZenoCommand(new byte[]{ 0x50, (byte)0xFF, 0x01, 0x00, 0x08, 0x00, (byte)0xFF, 0x31 }));
+
+        this.presetCmd.put("call",    new ZenoCommand(new byte[]{ 0x41, (byte)0xFF, 0x01, 0x00, 0x00 }));
+        this.presetCmd.put("save",    new ZenoCommand(new byte[]{ 0x32, (byte)0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+                                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }));
+        this.presetCmd.put("delete",  new ZenoCommand(new byte[]{ 0x32, (byte)0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }));
     }
 
-    public byte[] getCmd(String control, String action, int value) {
+    public byte[] getPtzCmd(String control, String action, int value) {
         if (StringUtils.equalsIgnoreCase("start", action)) {
             ZenoCommand command = this.startCmd.get(control);
             if (command != null) {
@@ -53,8 +60,28 @@ public class CctvControlZenoService {
         return null;
     }
 
+    public byte[] getPresetCmd(String action, Integer psetNmbr, Integer speed, Integer time, String name) {
+        ZenoCommand command = this.presetCmd.get(action);
+        if (command != null) {
+            byte[] cmd = command.getCmd();
+            int cmdPsetNmbr = Math.min(psetNmbr, 0xFF);
+            cmd[4] = (byte)cmdPsetNmbr;
+            if (StringUtils.equalsIgnoreCase("save", action)) {
+                int cmdSpeed = Math.min(speed, 0x3F);
+                int cmdTime = Math.min(time, 60);
+                cmd[6] = (byte)cmdSpeed;
+                cmd[8] = (byte)cmdTime;
+                String psetName = "preset " + cmdPsetNmbr;
+                byte[] byteName = psetName.getBytes();
+                System.arraycopy(byteName, 0, cmd, 10, byteName.length);
+            }
+            return cmd;
+        }
+        return null;
+    }
+
     public static class ZenoCommand {
-        private byte[] cmd;
+        private final byte[] cmd;
         public ZenoCommand(byte[] cmd) {
             this.cmd = cmd;
         }
@@ -62,7 +89,7 @@ public class CctvControlZenoService {
             if (this.cmd == null) {
                 return null;
             }
-            byte cmd[] = new byte[this.cmd.length];
+            byte[] cmd = new byte[this.cmd.length];
             System.arraycopy(this.cmd, 0, cmd, 0, this.cmd.length);
 
             int cs = 0;

+ 80 - 15
src/main/java/com/its/api/its/service/cctv/TbCctvPsetService.java

@@ -1,5 +1,6 @@
 package com.its.api.its.service.cctv;
 
+import com.its.api.its.model.dto.cctv.TbCctvCtlrDto;
 import com.its.api.its.model.dto.cctv.TbCctvPsetDto;
 import com.its.api.its.model.entity.cctv.TbCctvPset;
 import com.its.api.its.model.entity.cctv.TbCctvPsetKey;
@@ -9,17 +10,16 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Optional;
+import java.util.*;
 
 @Slf4j
 @RequiredArgsConstructor
 @Service
 public class TbCctvPsetService {
 
+    private final TbCctvCtlrService cctvService;
     private final TbCctvPsetRepository repo;
+    private final int MAX_PRESET = 6;
 
     // 데이터 1건 조회, 없으면 exception
     private TbCctvPset requireOne(TbCctvPsetKey id) {
@@ -27,22 +27,71 @@ public class TbCctvPsetService {
                 .orElseThrow(() -> new NoSuchElementException("데이터가 존재하지 않습니다: " + id));
     }
 
-    // 전체 데이터 조회
+    public Map<String, TbCctvPsetDto> getInitPsetInfo(Long cctvMngmNmbr) {
+        Map<String, TbCctvPsetDto> psetMap = new HashMap<>();
+        for (int ii = 1; ii <= this.MAX_PRESET; ii++) {
+            psetMap.put(String.valueOf(ii), TbCctvPsetDto.builder()
+                    .cctvMngmNmbr(cctvMngmNmbr)
+                    .psetNm("")
+                    .psetNmbr(String.valueOf(ii))
+                    .panVal(0)
+                    .tiltVal(0)
+                    .zoomVal(0)
+                    .focsVal(0)
+                    .dfltYn("N")
+                    .build());
+        }
+        return psetMap;
+    }
+    /**
+     * 프리셋 전체 조회
+     * @return
+     */
     @Transactional(readOnly = true)
     public List<TbCctvPsetDto> findAll() {
+
         List<TbCctvPsetDto> result = new ArrayList<>();
+        Map<Long, Map<String, TbCctvPsetDto>> cctvMap = new HashMap<>();
+        List<TbCctvCtlrDto> cctv = this.cctvService.findAllList();
+        cctv.forEach(obj -> {
+            cctvMap.put(obj.getCctvMngmNmbr(), getInitPsetInfo(obj.getCctvMngmNmbr()));
+        });
+
         List<TbCctvPset> data = this.repo.findAll();
-        for (TbCctvPset entity : data) {
-            result.add(entity.toDto());
-        }
+        data.forEach(obj -> {
+            TbCctvPsetDto dto = obj.toDto();
+            Map<String, TbCctvPsetDto> psetMap = cctvMap.get(obj.getCctvMngmNmbr());
+            if (psetMap != null) {
+                psetMap.put(obj.getPsetNmbr(), dto);
+            }
+        });
+        cctvMap.forEach((key, value) -> {
+            value.forEach((psetNmbr, dto) -> {
+                result.add(dto);
+            });
+        });
         return result;
     }
 
-    // 데이터 1건 조회(기존 데이터가 반드시 존재해야 함)
+    /**
+     * 해당 CCTV 의 프리셋 정보를 조회한다.
+     * 프리셋 정보가 존재하지 않음면 기본 프리셋 정보를 리턴한다.
+     * @param id
+     * @return
+     */
     @Transactional(readOnly = true)
-    public TbCctvPsetDto findById(TbCctvPsetKey id) {
-        TbCctvPset entity = requireOne(id);
-        return entity.toDto();
+    public List<TbCctvPsetDto> findListById(Long id) {
+        List<TbCctvPsetDto> result = new ArrayList<>();
+        Map<String, TbCctvPsetDto> defPset = getInitPsetInfo(id);
+        List<TbCctvPset> data = this.repo.findListById(id);
+        data.forEach(obj -> {
+            TbCctvPsetDto dto = obj.toDto();
+            defPset.put(obj.getPsetNmbr(), dto);
+        });
+        defPset.forEach((key, dto) -> {
+            result.add(dto);
+        });
+        return result;
     }
 
     // 데이터 변경
@@ -75,10 +124,26 @@ public class TbCctvPsetService {
     }
 
     // 정보 삭제-개별, 데이터 존재하지 않으면 Exception
+
+    /**
+     * 해당 프리셋 정보를 삭제한다(빈정보로 업데이트)
+     * @param id
+     * @param psetNmbr
+     * @return
+     */
     @Transactional
-    public TbCctvPsetDto deleteById(TbCctvPsetKey id) {
-        TbCctvPset entity = requireOne(id);
-        this.repo.deleteById(id);
+    public TbCctvPsetDto deleteById(Long id, String psetNmbr) {
+        TbCctvPset entity = TbCctvPset.builder()
+                .cctvMngmNmbr(id)
+                .psetNmbr(psetNmbr)
+                .psetNm("")
+                .tiltVal(0)
+                .panVal(0)
+                .focsVal(0)
+                .zoomVal(0)
+                .dfltYn("N")
+                .build();
+        this.repo.save(entity);
         return entity.toDto();
     }
 

+ 2 - 0
src/main/java/com/its/api/scheduler/job/CctvPsetScnrJobThread.java

@@ -3,6 +3,7 @@ package com.its.api.scheduler.job;
 import com.its.api.its.model.dto.cctv.TbCctvScnrDto;
 import com.its.api.its.model.entity.cctv.TbCctvScnr;
 import com.its.api.its.repository.cctv.TbCctvScnrRepository;
+import com.its.api.its.service.cctv.CctvControlService;
 import com.its.api.utils.ItsUtils;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -19,6 +20,7 @@ import java.util.List;
 public class CctvPsetScnrJobThread {
 
     private final TbCctvScnrRepository cctvScnrRepo;
+    private final CctvControlService cctvControlService;
 
     @Async("cctvPsetScnrExecutor")
     public void run() {