|
@@ -0,0 +1,680 @@
|
|
|
|
+package com.icontrols.kakao.socket.wallpad;
|
|
|
|
+
|
|
|
|
+import java.net.URI;
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
+import java.util.Base64;
|
|
|
|
+import java.util.Date;
|
|
|
|
+import java.util.HashMap;
|
|
|
|
+import java.util.List;
|
|
|
|
+import java.util.Map;
|
|
|
|
+import java.util.UUID;
|
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
|
+
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
+import javax.xml.xpath.XPath;
|
|
|
|
+import javax.xml.xpath.XPathConstants;
|
|
|
|
+import javax.xml.xpath.XPathExpressionException;
|
|
|
|
+import javax.xml.xpath.XPathFactory;
|
|
|
|
+
|
|
|
|
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
|
+import org.apache.http.impl.client.HttpClients;
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
|
+import org.springframework.http.MediaType;
|
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
|
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
+import org.springframework.util.LinkedMultiValueMap;
|
|
|
|
+import org.springframework.util.MultiValueMap;
|
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
|
+import org.w3c.dom.Document;
|
|
|
|
+import org.w3c.dom.Node;
|
|
|
|
+import org.w3c.dom.NodeList;
|
|
|
|
+
|
|
|
|
+import com.icontrols.kakao.common.ERROR_INFO;
|
|
|
|
+import com.icontrols.kakao.common.KAKAO_RESULT;
|
|
|
|
+import com.icontrols.kakao.common.KEY_INFO;
|
|
|
|
+import com.icontrols.kakao.common.SERVERINFO;
|
|
|
|
+import com.icontrols.kakao.common.THINGS_INFO;
|
|
|
|
+import com.icontrols.kakao.common.TYPE;
|
|
|
|
+import com.icontrols.kakao.common.TYPE.XML_LIGHT;
|
|
|
|
+import com.icontrols.kakao.common.utils.BeanUtils;
|
|
|
|
+import com.icontrols.kakao.common.utils.CommonUtil;
|
|
|
|
+import com.icontrols.kakao.config.AsyncConfig;
|
|
|
|
+import com.icontrols.kakao.entity.DonghoInfo;
|
|
|
|
+import com.icontrols.kakao.repo.DonghoInfoRepository;
|
|
|
|
+import com.icontrols.kakao.socket.utils.IPGenerator;
|
|
|
|
+import com.icontrols.kakao.socket.utils.SSLClient;
|
|
|
|
+import com.icontrols.kakao.socket.utils.XMLConstants.REMOTE_KIND;
|
|
|
|
+import com.icontrols.kakao.socket.utils.XMLGenerator;
|
|
|
|
+
|
|
|
|
+@Service("kakaoMsgHandler")
|
|
|
|
+public class KakaoMsgHandler {
|
|
|
|
+ private final static Logger logger = LoggerFactory.getLogger(KakaoMsgHandler.class);
|
|
|
|
+
|
|
|
|
+ @Resource(name="asyncConfig")
|
|
|
|
+ private AsyncConfig asyncConfig;
|
|
|
|
+
|
|
|
|
+ @Resource(name="wallpadMsgSender")
|
|
|
|
+ WallpadMsgSender wallpadMsgSender;
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ DonghoInfoRepository donghoRepo;
|
|
|
|
+
|
|
|
|
+ private static final int REPORTSTATE_REQUEST_FIRST = 0;
|
|
|
|
+ private static final int REPORTSTATE_REQUEST_RETRY = 1;
|
|
|
|
+ /**
|
|
|
|
+ * 카카오 전용 인증 프로세스
|
|
|
|
+ * 월패드에 인증번호를 노출시키는 메시지를 전송한다.
|
|
|
|
+ * @param param
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public Map<String, Object> doAuthProcess(Map<String, Object> param){
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ int dong = Integer.parseInt((String)param.get("dong"));
|
|
|
|
+ int ho = Integer.parseInt((String)param.get("ho"));
|
|
|
|
+ String code = (String)param.get("code");
|
|
|
|
+
|
|
|
|
+ String targetIP = IPGenerator.getIPFromDongHo(dong, ho);
|
|
|
|
+ String twinid = UUID.randomUUID().toString();
|
|
|
|
+ String alias = "Kakao";
|
|
|
|
+ StringBuffer sb = new StringBuffer();
|
|
|
|
+
|
|
|
|
+ sb.append(XMLGenerator.MakeXMLHeaderForNotice(REMOTE_KIND.remote_register_new_device_auth_number.getContents()))
|
|
|
|
+ .append(" <home_info dong=\"").append(dong).append("\" ho=\"").append(ho).append("\" ip=\"").append(targetIP).append("\" type=\"main\"/>\r\n")
|
|
|
|
+ .append(" <device_info alias=\"").append(alias).append("\" twinid=\"").append(twinid).append("\" auth_number=\"" + code + "\" />\r\n")
|
|
|
|
+ .append(XMLGenerator.MakeXMLTail());
|
|
|
|
+
|
|
|
|
+ logger.info(sb.toString());
|
|
|
|
+
|
|
|
|
+ SSLClient.sendToWallpad(targetIP, sb.toString());
|
|
|
|
+
|
|
|
|
+ retMap.put("result", ERROR_INFO.CODE.SUCCESS.getContents());
|
|
|
|
+ retMap.put("resultCode", ERROR_INFO.CODE.SUCCESS.getmIdx());
|
|
|
|
+ }catch(Exception e) {
|
|
|
|
+ retMap.put("result", ERROR_INFO.CODE.FAIL_999.getContents());
|
|
|
|
+ retMap.put("resultCode", ERROR_INFO.CODE.FAIL_999.getmIdx());
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 기기 구독 / 해제 에 대한 메시지를 월패드에 전송한다.
|
|
|
|
+ * 기기 구독이 돼 있지 않으면 이벤트를 받지 못한다.
|
|
|
|
+ * @param param
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public Map<String, Object> subscriptionProcess(Map<String, Object> param) {
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ Document doc = new WallpadMsgSender().remoteDeviceSubscription(param);
|
|
|
|
+
|
|
|
|
+ if(doc == null) {
|
|
|
|
+ logger.error("[subscriptionProcess ERROR] - Document null");
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_UNREACHABLE));
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ retMap.put("result", "success");
|
|
|
|
+ }catch(Exception e) {
|
|
|
|
+ logger.error("[subscriptionProcess ERROR] - " + e.getStackTrace());
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_INTERNAL));
|
|
|
|
+ }
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 월패드와 연결된 기기들의 전체 상태를 월패드에 요청하고 응답 받는다.
|
|
|
|
+ * 지원 기기가 추가되면 해당 부분은 수정이 필요하다.
|
|
|
|
+ * 자식 노드로 subdevinfo가 있는 기기들을 잘 확인해야 한다.
|
|
|
|
+ * @param dong
|
|
|
|
+ * @param ho
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public Map<String, Object> getDevicesStatusList(int dong, int ho) {
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+ Map<String, Object> body = new HashMap<String, Object>();
|
|
|
|
+ List<String> unreachableIds = new ArrayList<String>();
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ Document doc = new WallpadMsgSender().remoteDeviceStatusList(dong, ho);
|
|
|
|
+
|
|
|
|
+ if(doc == null) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_UNREACHABLE));
|
|
|
|
+ logger.error("[ERROR]-[Document is Null] - [" + dong + "-" + ho + "]");
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ XPath xpath = XPathFactory.newInstance().newXPath();
|
|
|
|
+ Node iMap = (Node)xpath.evaluate("//imap/service", doc, XPathConstants.NODE);
|
|
|
|
+ String xmlResult = iMap.getAttributes().getNamedItem("result").getTextContent();
|
|
|
|
+
|
|
|
|
+ logger.debug("[KakaoMsgHandler - getDeviceList] - xmlResult -> " + xmlResult);
|
|
|
|
+
|
|
|
|
+ if(!xmlResult.equalsIgnoreCase("ok")) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_UNREACHABLE));
|
|
|
|
+ logger.error("[KakaoMsgHandler - getDeviceList] xmlResult not ok");
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 월패드와 연결된 상위 THINGS 갯수 확인
|
|
|
|
+ Node dCount = null;
|
|
|
|
+
|
|
|
|
+ NodeList devList = (NodeList)xpath.evaluate("//imap/service/devinfo", doc, XPathConstants.NODESET);
|
|
|
|
+ for(int i=0; i < devList.getLength(); i++) {
|
|
|
|
+ Node pNode = devList.item(i);
|
|
|
|
+
|
|
|
|
+ String tmpModelId = pNode.getAttributes().getNamedItem("name").getTextContent();
|
|
|
|
+ int childCount = Integer.parseInt(pNode.getAttributes().getNamedItem("value").getTextContent());
|
|
|
|
+
|
|
|
|
+ // 아직 카카오에서 electric과 batchlight는 지원하지 않는다.
|
|
|
|
+ // 나중에 지원하게 되면 그때 추가하면 된다.
|
|
|
|
+ if(tmpModelId.equalsIgnoreCase("electric") || tmpModelId.equalsIgnoreCase("batchlight")) continue;
|
|
|
|
+
|
|
|
|
+ List<Map<String, Object>> childNodeInfoList = null;
|
|
|
|
+
|
|
|
|
+ // subdevinfo 를 가지고 있는 THINGS일 경우는 subdevinfo 들을 다시 구해야 한다.
|
|
|
|
+ // 예를들어 light는 light01 ~ 05 등의 subdevinfo를 가진다.
|
|
|
|
+ if(tmpModelId.equalsIgnoreCase("light")) {
|
|
|
|
+ for(int j=1; j <= childCount; j++) {
|
|
|
|
+ String childName = j < 10 ? tmpModelId+"0"+j : tmpModelId+j;
|
|
|
|
+ Node childNode = (Node)xpath.evaluate("//imap/service/devinfo/*[@name='"+ childName +"']", doc, XPathConstants.NODE);
|
|
|
|
+
|
|
|
|
+ childCount = Integer.parseInt(childNode.getAttributes().getNamedItem("value").getTextContent());
|
|
|
|
+
|
|
|
|
+ NodeList childList = (NodeList)xpath.evaluate("//imap/service/devinfo/subdevinfo/*[@name='"+ childName +"']", doc, XPathConstants.NODESET);
|
|
|
|
+ childNodeInfoList = getChildDeviceInfo(childCount, childList);
|
|
|
|
+
|
|
|
|
+ if(childNodeInfoList != null)
|
|
|
|
+ for(Map<String, Object> tMap : childNodeInfoList) body.putAll(tMap);
|
|
|
|
+ }
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ // 해당 THINGS의 자식노드 status 목록들을 조회한다.
|
|
|
|
+ NodeList childList = (NodeList)xpath.evaluate("//imap/service/devinfo/*[@name='"+ tmpModelId +"']", doc, XPathConstants.NODESET);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ childNodeInfoList = getChildDeviceInfo(childCount, childList);
|
|
|
|
+
|
|
|
|
+ if(childNodeInfoList != null)
|
|
|
|
+ for(Map<String, Object> tMap : childNodeInfoList) body.putAll(tMap);
|
|
|
|
+ }
|
|
|
|
+ }catch(Exception e) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_INTERNAL));
|
|
|
|
+ logger.error("[ERROR] - " + e.getMessage());
|
|
|
|
+ }finally {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_UNREACHABLEIDS, unreachableIds);
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_BODY, body);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 동 호 정보 EndpointID 로 월패드에 상태 조회 메세지를 전송하고 결과를 반환 받는다.
|
|
|
|
+ * @param dong
|
|
|
|
+ * @param ho
|
|
|
|
+ * @param endpointId
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public Map<String, Object> getDeviceStatus(int dong, int ho, String endpointId){
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ String[] tmpSplit = endpointId.split("_");
|
|
|
|
+
|
|
|
|
+ String modelId = tmpSplit[0]; // 기기 ID
|
|
|
|
+ int devNum = Integer.parseInt(tmpSplit[1]); // 회로 번호
|
|
|
|
+
|
|
|
|
+ Document doc = new WallpadMsgSender().remoteDeviceStatus(dong, ho, modelId, devNum);
|
|
|
|
+
|
|
|
|
+ if(doc == null) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_UNREACHABLE));
|
|
|
|
+ logger.error("[ERROR]-[Document is Null] - [" + dong + "-" + ho + "] ["+ modelId +"]");
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return getStatusMap(endpointId, doc);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 월패드에 장치 제어 명령을 보내고 결과를 리턴한다.
|
|
|
|
+ * @param dong
|
|
|
|
+ * @param ho
|
|
|
|
+ * @param endpointId
|
|
|
|
+ * @param actionName
|
|
|
|
+ * @param desired
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public Map<String, Object> sendDeviceAction(int dong, int ho, String endpointId, String actionName, Map<String, Object> desired){
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ String[] tmpSplit = endpointId.split("_");
|
|
|
|
+ String modelId = tmpSplit[0];
|
|
|
|
+ int devNum = tmpSplit.length == 1?1:Integer.parseInt(tmpSplit[1]);
|
|
|
|
+
|
|
|
|
+ THINGS_INFO.THINGS things = THINGS_INFO.getThingsType(endpointId);
|
|
|
|
+
|
|
|
|
+ if(things == null) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_INVALID_REQ));
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+ String action = KakaoMsgHandler.setDeviceActionStr(modelId, desired);
|
|
|
|
+
|
|
|
|
+ if(action.length() == 0) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_INVALID_REQ));
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+ Document doc = new WallpadMsgSender().remoteDeviceControl(
|
|
|
|
+ dong,
|
|
|
|
+ ho,
|
|
|
|
+ modelId,
|
|
|
|
+ devNum,
|
|
|
|
+ action
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ if(doc == null) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_UNREACHABLE));
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return getStatusMap(endpointId, doc);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 상태조회 요청 메세지에 대한 응답 메세지를 파싱하여 카카오 형식으로 변환한다.
|
|
|
|
+ * @param endpointId
|
|
|
|
+ * @param doc
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static Map<String, Object> getStatusMap(String endpointId, Document doc){
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ XPath xpath = XPathFactory.newInstance().newXPath();
|
|
|
|
+ try {
|
|
|
|
+ Node iMap = (Node) xpath.evaluate("//imap/service", doc, XPathConstants.NODE);
|
|
|
|
+ String xmlResult = iMap.getAttributes().getNamedItem("result").getTextContent();
|
|
|
|
+
|
|
|
|
+ if(!xmlResult.equalsIgnoreCase("ok")) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_INVALID_REQ));
|
|
|
|
+ logger.error("[ERROR]-[xmlResult is not ok");
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String status = (String) xpath.evaluate("//imap/service/status", doc, XPathConstants.STRING);
|
|
|
|
+
|
|
|
|
+ Map<String, Object> state = setDeviceState(endpointId, status);
|
|
|
|
+ retMap.put(endpointId, state);
|
|
|
|
+
|
|
|
|
+ } catch (XPathExpressionException e) {
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ERROR, KAKAO_RESULT.getErrorInfo(KAKAO_RESULT.FAIL_INTERNAL));
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 상태 변경 이벤트 발생시 이벤트 결과를 카카오 이벤트 서버에 전송한다.
|
|
|
|
+ * 전송시 토큰이 만료 되면 [sendKakaoTokenRefresh] method를 호출하여 토큰을 갱신한뒤 다시 재시도 한다.
|
|
|
|
+ * @param dong
|
|
|
|
+ * @param ho
|
|
|
|
+ * @param params
|
|
|
|
+ * @param reqType
|
|
|
|
+ */
|
|
|
|
+ public void SendStatusResult(int dong, int ho, Map<String, Object> params, int reqType) {
|
|
|
|
+ try {
|
|
|
|
+
|
|
|
|
+ if(params == null) return;
|
|
|
|
+
|
|
|
|
+ // 먼저 이벤트 전송전 인증 토큰이 만료 됐는지 확인한다.
|
|
|
|
+ // 인증 토큰이 만료 됐으면 인증토큰을 재생성 한뒤 이벤트를 전송한다.
|
|
|
|
+ // reqType이 REPORTSTATE_REQUEST_FIRST 이면 이벤트 전송을 한번도 하지 않은 경우
|
|
|
|
+ // reqType이 REPORTSTATE_REQUEST_RETRY 이면 이벤트 전송 후 인증토큰 만료 에러 때문에 인증토큰 갱신하고 재전송하는 경우
|
|
|
|
+ DonghoInfoRepository repo = (DonghoInfoRepository)BeanUtils.getBean("donghoInfoRepository");
|
|
|
|
+ List<DonghoInfo> dList = repo.findByDongAndHo(Integer.toString(dong), Integer.toString(ho));
|
|
|
|
+
|
|
|
|
+ if(dList.size() != 1) {
|
|
|
|
+ logger.error("[" + dong + "/"+ ho +"]-유저정보 없음- 이벤트 전송 불가.");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String tokenStartTime = dList.get(0).getKakaoTokenCreateTime();
|
|
|
|
+ String tokenExpiresTime = dList.get(0).getKakaoTokenExpireTime();
|
|
|
|
+ String currentTime = CommonUtil.getStrTypeCurTime();
|
|
|
|
+
|
|
|
|
+ int result = CommonUtil.compareStringTypeTime(currentTime, tokenExpiresTime);
|
|
|
|
+
|
|
|
|
+ // 현재 시간이 토큰 만료시간보다 크다면 토큰이 만료 됐다는 뜻.
|
|
|
|
+ // 카카오 이벤트 서버에서 토큰을 갱신 받은 뒤 이벤트를 전송해야 한다.
|
|
|
|
+ if(result < 1) {
|
|
|
|
+ sendKakaoTokenRefresh(dong, ho, params);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String authorization = SERVERINFO.KAKAO_EVENT_CLIENT_ID+":"+SERVERINFO.KAKAO_EVENT_CLIENT_SECRET;
|
|
|
|
+ String encoding = Base64.getEncoder().encodeToString(authorization.getBytes());
|
|
|
|
+
|
|
|
|
+ CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
|
|
|
|
+ HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
|
|
|
+
|
|
|
|
+ requestFactory.setHttpClient(httpClient);
|
|
|
|
+ requestFactory.setConnectTimeout(3000);
|
|
|
|
+
|
|
|
|
+ RestTemplate restTemplate = new RestTemplate(requestFactory);
|
|
|
|
+
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
+
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
+ headers.set("Authorization", "Basic " + encoding);
|
|
|
|
+ // 제어기기 별로 상태 이벤트 구분하여 처리하도록 해야 한다
|
|
|
|
+ HttpEntity<Object> entity = new HttpEntity<Object>(params, headers);
|
|
|
|
+
|
|
|
|
+ String url = SERVERINFO.SendDevEventURL + ":" + SERVERINFO.SendDevEventPort + "/events";
|
|
|
|
+
|
|
|
|
+ logger.info("SendStatusResult URL : " + url);
|
|
|
|
+ logger.info("SendStatusResult header : " + headers.toString());
|
|
|
|
+ logger.info("SendStatusResult return value : " + params.toString());
|
|
|
|
+
|
|
|
|
+ String answer = restTemplate.postForObject(url, entity, String.class);
|
|
|
|
+
|
|
|
|
+ logger.info("SendStatusResult answer :" + answer);
|
|
|
|
+
|
|
|
|
+ // 결과에 error가 있으면 error에 맞게 대응해줘야 한다.
|
|
|
|
+ Map<String, Object> retMap = CommonUtil.jsonToMap(answer);
|
|
|
|
+
|
|
|
|
+ if(retMap.containsKey(KEY_INFO.HTTP_KEY_ERROR)) {
|
|
|
|
+ //error 결과가 -201 이면 인증토큰이 만료 됐다는 뜻
|
|
|
|
+ Map<String, Object> errMap = (Map<String, Object>)retMap.get(KEY_INFO.HTTP_KEY_ERROR);
|
|
|
|
+ int errorCode = Integer.parseInt((String)errMap.get(KEY_INFO.ERROR_KEY_CODE));
|
|
|
|
+ String message = (String)errMap.get(KEY_INFO.ERROR_KEY_MESSAGE);
|
|
|
|
+
|
|
|
|
+ logger.info("[KAKAO - ERROR] - [ CODE ] - " + errorCode + " || [message] - " + message );
|
|
|
|
+
|
|
|
|
+ // 인증토큰 만료 에러
|
|
|
|
+ if(reqType == REPORTSTATE_REQUEST_FIRST && errorCode == KAKAO_RESULT.CODE.FAIL_EXPIREDTOKEN.getResultCode()) {
|
|
|
|
+ // 인증토큰 갱신 요청 보내기
|
|
|
|
+ sendKakaoTokenRefresh(dong, ho, params);
|
|
|
|
+ }
|
|
|
|
+ }else {
|
|
|
|
+ logger.info("EVENT SEND SUCCESS -- " + params.toString());
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ logger.info("SendStatusResult Exception e :" + e.toString());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 이벤트 전송시 토큰이 만료되면 해당 메서드가 호출된다.
|
|
|
|
+ * 이벤트 토큰 만료시 카카오 이벤트 토큰 서버에 토큰 갱신 요청을 보낸다.
|
|
|
|
+ * 토큰이 갱신 되면 이벤트 전송을 시도한다.
|
|
|
|
+ * @param dong
|
|
|
|
+ * @param ho
|
|
|
|
+ * @param params
|
|
|
|
+ */
|
|
|
|
+ public boolean sendKakaoTokenRefresh(int dong, int ho, Map<String, Object> params){
|
|
|
|
+ DonghoInfoRepository repo = (DonghoInfoRepository)BeanUtils.getBean("donghoInfoRepository");
|
|
|
|
+ List<DonghoInfo> dList = repo.findByDongAndHo(Integer.toString(dong), Integer.toString(ho));
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if(dList.size() != 1) return false;
|
|
|
|
+ DonghoInfo donghoInfo = dList.get(0);
|
|
|
|
+
|
|
|
|
+ String refreshToken = donghoInfo.getKakaoRefreshToken();
|
|
|
|
+
|
|
|
|
+ String authorization = SERVERINFO.KAKAO_EVENT_CLIENT_ID+":"+SERVERINFO.KAKAO_EVENT_CLIENT_SECRET;
|
|
|
|
+ String encoding = Base64.getEncoder().encodeToString(authorization.getBytes());
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
|
|
+ headers.add("Authorization", "Basic " + encoding);
|
|
|
|
+
|
|
|
|
+ MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
|
|
|
+ parameters.add("grant_type", "refresh_token");
|
|
|
|
+ parameters.add("refresh_token", refreshToken);
|
|
|
|
+
|
|
|
|
+ HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(parameters, headers);
|
|
|
|
+
|
|
|
|
+ String url = SERVERINFO.SendDevEventURL + ":" + SERVERINFO.SendDevEventPort + "/events/token";
|
|
|
|
+
|
|
|
|
+ logger.info("[TOKEN REFRESH REQUEST] URL : " + url);
|
|
|
|
+ logger.info("[TOKEN REFRESH REQUEST] header : " + headers.toString());
|
|
|
|
+ logger.info("[TOKEN REFRESH REQUEST] return value : " + entity.toString());
|
|
|
|
+ URI uri = URI.create(url);
|
|
|
|
+
|
|
|
|
+ CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
|
|
|
|
+ HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
|
|
|
+
|
|
|
|
+ requestFactory.setHttpClient(httpClient);
|
|
|
|
+ requestFactory.setConnectTimeout(3000);
|
|
|
|
+
|
|
|
|
+ RestTemplate restTemplate = new RestTemplate(requestFactory);
|
|
|
|
+
|
|
|
|
+ String answer = restTemplate.postForObject(url, entity, String.class);
|
|
|
|
+ logger.debug("[TOKEN REFRESH REQUEST] = " + answer);
|
|
|
|
+
|
|
|
|
+ Map<String, Object> retMap = CommonUtil.jsonToMap(answer);
|
|
|
|
+
|
|
|
|
+ if(retMap.containsKey(KEY_INFO.HTTP_KEY_ACCESS_TOKEN) && retMap.containsKey(KEY_INFO.HTTP_KEY_REFRESH_TOKEN) && retMap.containsKey(KEY_INFO.HTTP_KEY_EXPIRES_IN)) {
|
|
|
|
+ donghoInfo.setKakaoAccessToken((String)retMap.get(KEY_INFO.HTTP_KEY_ACCESS_TOKEN));
|
|
|
|
+ donghoInfo.setKakaoRefreshToken((String)retMap.get(KEY_INFO.HTTP_KEY_REFRESH_TOKEN));
|
|
|
|
+ donghoInfo.setKakaoExpiresTime(Integer.toString((int)retMap.get(KEY_INFO.HTTP_KEY_EXPIRES_IN)));
|
|
|
|
+
|
|
|
|
+ donghoInfo.setKakaoTokenCreateTime(CommonUtil.getStrTypeCurTime());
|
|
|
|
+ donghoInfo.setKakaoTokenExpireTime(CommonUtil.getStrTypeCurTimeAfterSec(Integer.parseInt(donghoInfo.getKakaoExpiresTime())));
|
|
|
|
+
|
|
|
|
+ repo.save(donghoInfo);
|
|
|
|
+
|
|
|
|
+ SendStatusResult(dong, ho, params, REPORTSTATE_REQUEST_FIRST);
|
|
|
|
+ return true;
|
|
|
|
+ }else {
|
|
|
|
+ logger.error("****************************************************");
|
|
|
|
+ logger.error("토클 리프레시 실패");
|
|
|
|
+ logger.error("****************************************************");
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 이벤트 발생시 카카오에 전송하는 Body를 반환한다.
|
|
|
|
+ * @param dong
|
|
|
|
+ * @param ho
|
|
|
|
+ * @param body
|
|
|
|
+ * @return
|
|
|
|
+ * @throws Exception
|
|
|
|
+ */
|
|
|
|
+ public Map<String, Object> setKakaoEventMsg(int dong, int ho, Map<String, Object> body) throws Exception{
|
|
|
|
+ Map<String, Object> retMap = new HashMap<String, Object>();
|
|
|
|
+ List<String> unreachableIds = new ArrayList<>();
|
|
|
|
+ DonghoInfoRepository repo = (DonghoInfoRepository)BeanUtils.getBean("donghoInfoRepository");
|
|
|
|
+ List<DonghoInfo> dList = repo.findByDongAndHo(Integer.toString(dong), Integer.toString(ho));
|
|
|
|
+
|
|
|
|
+ if(dList.size() != 1) return null;
|
|
|
|
+
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_ID, UUID.randomUUID().toString());
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_TYPE, TYPE.REQ_TYPE.REPORTSTATE.getTypeName());
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_AUTHORIZATION, dList.get(0).getKakaoAccessToken());
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_VERSION, "1");
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_UNREACHABLEIDS, unreachableIds);
|
|
|
|
+ retMap.put(KEY_INFO.HTTP_KEY_BODY, body);
|
|
|
|
+
|
|
|
|
+ return retMap;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 지원하는 기기들의 Capability 를 카카오 형식에 맞게 추출한다.
|
|
|
|
+ * 기기가 추가되게 되면 목록을 수정해야한다.
|
|
|
|
+ * @param modelId
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static Map<String, Object> getDeviceCapabilities(String modelId){
|
|
|
|
+ THINGS_INFO.THINGS things = THINGS_INFO.getThingsType(modelId);
|
|
|
|
+
|
|
|
|
+ String jsonString = "";
|
|
|
|
+
|
|
|
|
+ switch(things.getEndPoint()) {
|
|
|
|
+ case THINGS_INFO.THINGS_LIGHT:
|
|
|
|
+ jsonString = THINGS_INFO.THINGS_CAPABILITY.LIGHT.getCapJsonStr();
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_TEMPER:
|
|
|
|
+ jsonString = THINGS_INFO.THINGS_CAPABILITY.TEMPER.getCapJsonStr();
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_GAS:
|
|
|
|
+ jsonString = THINGS_INFO.THINGS_CAPABILITY.GAS.getCapJsonStr();
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_VENTIL:
|
|
|
|
+ jsonString = THINGS_INFO.THINGS_CAPABILITY.VENTIL.getCapJsonStr();
|
|
|
|
+ break;
|
|
|
|
+ case THINGS_INFO.THINGS_ELECPLUG:
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Map<String, Object> capabilities = CommonUtil.jsonToMap(jsonString);
|
|
|
|
+
|
|
|
|
+ return capabilities;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Document에서 추출한 Device의 자식 노드들을 추출한다.
|
|
|
|
+ * @param childCount
|
|
|
|
+ * @param childList
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public List<Map<String, Object>> getChildDeviceInfo(int childCount, NodeList childList){
|
|
|
|
+ List<Map<String, Object>> deviceList = new ArrayList<>();
|
|
|
|
+
|
|
|
|
+ for(int j=0; j < childCount; j++) {
|
|
|
|
+ Map<String, Object> deviceMap = new HashMap<>();
|
|
|
|
+
|
|
|
|
+ Node cNode = childList.item(j);
|
|
|
|
+ if(cNode == null) {
|
|
|
|
+ logger.error("cNode is null");
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String cNodeName = cNode.getAttributes().getNamedItem("name").getTextContent();
|
|
|
|
+ String cNodeDevNum = cNode.getAttributes().getNamedItem("dev_num").getTextContent();
|
|
|
|
+ String status = cNode.getTextContent();
|
|
|
|
+ String endpointId = cNodeName+"_"+cNodeDevNum;
|
|
|
|
+
|
|
|
|
+ THINGS_INFO.THINGS things = THINGS_INFO.getThingsType(cNodeName);
|
|
|
|
+
|
|
|
|
+ Map<String, Object> deviceInfo = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ deviceInfo.put(KEY_INFO.BODY_KEY_ENDPOINT_ID, endpointId);
|
|
|
|
+ deviceInfo.put(KEY_INFO.BODY_KEY_NAME, cNodeName.toUpperCase() + " " + cNodeDevNum);
|
|
|
|
+ deviceInfo.put(KEY_INFO.BODY_KEY_DISPLAY_TYPE, things.getKakaoDisplayName());
|
|
|
|
+
|
|
|
|
+ deviceInfo.putAll(getDeviceCapabilities(endpointId));
|
|
|
|
+ deviceInfo.put(KEY_INFO.BODY_KEY_STATE, setDeviceState(endpointId, status));
|
|
|
|
+
|
|
|
|
+ deviceMap.put(endpointId, deviceInfo);
|
|
|
|
+ deviceList.add(deviceMap);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return deviceList;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 디바이스의 상태 명령어를 디바이스 종류에 맞게 생성한다.
|
|
|
|
+ * @param modelId
|
|
|
|
+ * @param desired
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static String setDeviceActionStr(String modelId, Map<String, Object> desired) {
|
|
|
|
+ StringBuffer sb = new StringBuffer();
|
|
|
|
+
|
|
|
|
+ THINGS_INFO.THINGS things = THINGS_INFO.getThingsType(modelId);
|
|
|
|
+
|
|
|
|
+ switch(things.getEndPoint()) {
|
|
|
|
+ case THINGS_INFO.THINGS_LIGHT:
|
|
|
|
+ sb.append(desired.get("power"));
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_TEMPER:
|
|
|
|
+ if(desired.containsKey("power")) sb.append(desired.get("power"));
|
|
|
|
+ else if(desired.containsKey("targetSetpoint")) sb.append("on/").append(desired.get("targetSetpoint"));
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_GAS:
|
|
|
|
+ if(desired.containsKey("locked")){
|
|
|
|
+ if((boolean)desired.get("locked"))
|
|
|
|
+ sb.append("close");
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_VENTIL:
|
|
|
|
+ if(desired.containsKey("power")) sb.append(desired.get("power"));
|
|
|
|
+ else if(desired.containsKey("fanSpeed")) sb.append("on/").append(desired.get("fanSpeed"));
|
|
|
|
+ break;
|
|
|
|
+ case THINGS_INFO.THINGS_ELECPLUG:
|
|
|
|
+ if(desired.containsKey("power")) sb.append(desired.get("power"));
|
|
|
|
+ else if(desired.containsKey("mode")) sb.append(desired.get("mode"));
|
|
|
|
+ break;
|
|
|
|
+ case THINGS_INFO.THINGS_ELEVATOR:
|
|
|
|
+ if(desired.containsKey("callDirection")) {
|
|
|
|
+ if("down".equalsIgnoreCase((String)desired.get("callDirection"))) {
|
|
|
|
+ sb.append(desired.get("callDirection"));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ logger.info("[---->>]" + sb.toString());
|
|
|
|
+ return sb.toString();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 상태조회 이후 월패드에서 받은 상태 값을 디바이스의 속성에 맞게 파싱하고
|
|
|
|
+ * Map에 <"속성명", "상태값"> 형태로 담아준다.
|
|
|
|
+ * @param modelId
|
|
|
|
+ * @param status
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static Map<String, Object> setDeviceState(String modelId, String status){
|
|
|
|
+ Map<String,Object> state = new HashMap<String, Object>();
|
|
|
|
+
|
|
|
|
+ THINGS_INFO.THINGS things = THINGS_INFO.getThingsType(modelId);
|
|
|
|
+ String[] splitStr = status.split("\\/");
|
|
|
|
+
|
|
|
|
+ switch(things.getEndPoint()) {
|
|
|
|
+ case THINGS_INFO.THINGS_LIGHT:
|
|
|
|
+ state.put("power", status);
|
|
|
|
+ break;
|
|
|
|
+ case THINGS_INFO.THINGS_TEMPER:
|
|
|
|
+
|
|
|
|
+ state.put("power", splitStr[0]);
|
|
|
|
+ state.put("targetSetpoint", Double.parseDouble(splitStr[1]));
|
|
|
|
+ state.put("currentTemperature", Double.parseDouble(splitStr[2]));
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+ case THINGS_INFO.THINGS_GAS:
|
|
|
|
+ if(status.equalsIgnoreCase("close"))
|
|
|
|
+ state.put("locked", true);
|
|
|
|
+ else
|
|
|
|
+ state.put("locked", false);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case THINGS_INFO.THINGS_VENTIL:
|
|
|
|
+ state.put("power", splitStr[0]);
|
|
|
|
+ if(splitStr[0].equalsIgnoreCase("on")) state.put("fanSpeed", splitStr[1]);
|
|
|
|
+ break;
|
|
|
|
+ case THINGS_INFO.THINGS_ELEVATOR:
|
|
|
|
+ state.put("callDirection", "down");
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return state;
|
|
|
|
+ }
|
|
|
|
+}
|