투케이2K

175. (TWOK/UTIL) [Web/JavaScript] 자바스크립트 mqtt.min.js 사용해 AWS MQTT5 사용자속성 userProperties 메시지 전송 및 구독 본문

투케이2K 유틸파일

175. (TWOK/UTIL) [Web/JavaScript] 자바스크립트 mqtt.min.js 사용해 AWS MQTT5 사용자속성 userProperties 메시지 전송 및 구독

투케이2K 2025. 11. 29. 17:20
728x90

[설 명]

프로그램 : Web / JavaScript

설 명 : [Web/JavaScript] 자바스크립트 mqtt.min.js 사용해 AWS MQTT5 사용자속성 userProperties 메시지 전송 및 구독

 

[소스 코드]

-----------------------------------------------------------------------------------------
[사전 설명 및 설정 사항]
-----------------------------------------------------------------------------------------

- 개발 환경 : Web


- 개발 기술 : JavaScript (자바스크립트) / MQTT 5 / userProperties


- 사전) MQTT (Message Queuing Telemetry Transport) 설명 : 

  >> MQTT 는 경량 메시지 프로토콜로, 주로 IoT(사물인터넷) 환경에서 사용됩니다

  >> MQTT 목적 : 제한된 네트워크 환경(저속, 불안정)에서 효율적으로 메시지를 주고받기 위해 설계

  >> MQTT 기반 : TCP/IP 위에서 동작

  >> MQTT 패턴 : Publish/Subscribe 모델을 사용

    - Publisher : 메시지를 발행하는 클라이언트

    - Subscriber : 특정 주제(topic)를 구독하는 클라이언트

    - Broker: 메시지를 중개하는 서버 (예: Mosquitto)

  >> MQTT 주요 특징 : 

    - 경량성 : 헤더가 매우 작음(2바이트부터 시작)

    - QoS (Quality of Service) : 
      $ QoS 0: 최대 한 번 전달(보장 없음)
      $ QoS 1: 최소 한 번 전달(중복 가능)
      $ QoS 2: 정확히 한 번 전달(가장 안전)

    - 지속 연결 : KeepAlive로 연결 상태 유지

    - Last Will and Testament (LWT) : 클라이언트 비정상 종료 시 브로커가 메시지 발행

    - 토픽 기반 라우팅 : 계층적 구조(/home/temperature 등)

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[소스 코드]
-----------------------------------------------------------------------------------------

<!DOCTYPE HTML>
<html lang="ko">
<head>
    <title>javaScriptTest</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">


    <!-- 반응형 구조 만들기 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">


    <!-- 내부 CSS 스타일 지정 -->
    <style>

        html, body {
            width: 100%;
            height: 100%;
            margin : 0 auto;
            padding : 0;
            border : none;
            background-color: #666;
        }

    </style>





    <!-- [CDN 주소 설정] : https://www.jsdelivr.com/package/npm/paho-mqtt -->
    <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js"></script>






    <!-- [자바스크립트 코드 지정] -->
    <script type="module">

        // --------------------------------------------------------------------------------------------------------------

        // [모듈 import]
        import aws4 from "https://esm.sh/aws4fetch@1.0.17";

        const { AwsClient } = aws4;    

        // --------------------------------------------------------------------------------------------------------------

        // [전역 변수 선언]        
        var mqttClient = null;
        var connectTimer = null;

        const QOS = 0; // 네트워크 서비스 품질
        const CONNECT_TIME_OUT = 20; // Seconds
        const CONNECT_CHECK_TIME = 60; // Seconds
        const SUBSCRIBE_TIME_OUT = 20; // Seconds


        // [AWS 접속 계정 정보]
        const endpoint = 'a..km-ats.iot.ap-northeast-2.amazonaws.com'; // [AWS iot:Data-ATS 도메인]
        const region = 'ap-northeast-2'; // [AWS 리전]
        const accessKey = 'AK..A6'; // [IAM 액세스 키]
        const secretKey = 'mP..5J'; // [IAM 시크릿 키]


        // [AWS 생성 된 사물 Thing]
        const thingName = "T_TWOK_0000000001"; 

        // 메시지 전송 토픽
        const publish_json_empty = {
            state: {
                reported: { // Device Reported
                            
                }
            }
        };

        const publish_topic_common = `$aws/things/${thingName}/shadow/name/common/update`;
        
        // --------------------------------------------------------------------------------------------------------------

        // [html 최초 로드 및 이벤트 상시 대기 실시]
        window.onload = async function() {

            console.log("");
            console.log("=========================================");
            console.log("[window onload] : [start]");
            console.log("=========================================");
            console.log("");

            try {

                // --------------------------------------
                // Init : 변수 초기화 수행
                // --------------------------------------
                subscribeTopicList = new Array();


                // --------------------------------------
                // AWS : SigV4 인증 URL 생성
                // --------------------------------------
                const aws = new AwsClient({
                    accessKeyId: accessKey,
                    secretAccessKey: secretKey,
                    service: "iotdevicegateway", // ✅ 중요!
                    region: region,
                });

                const url = `wss://${endpoint}/mqtt`; // [AWS iot:Data-ATS 도메인]

                
                // [SigV4 서명된 URL 생성]
                const signed = await aws.sign(url, { 
                    method: "GET",
                    signQuery: true // ✅ signQuery: true 옵션 추가
                });

                
                // ✅ wss://a1y..pd9-ats.iot.ap-northeast-1.amazonaws.com/mqtt?X-Amz-Date=20251123T234432Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AK..H7Q%2F20251123%2Fap-northeast-1%2Fiotdevicegateway%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=fcc..d7a
                const signUrl = signed.url;


                if (signUrl == null || String(typeof signUrl).toLowerCase() == "undefined" || JSON.stringify(signUrl) == "null" || signUrl == "") {
                    
                    alert("[window onload] : [Error] : aws.sign url is null");
                    
                }
                else {
                    // --------------------------------------
                    // Call : Mqtt Connect
                    // --------------------------------------
                    mqttConnect(signUrl); // ✅ AWS Iot Core MQTT Connect WebSocket Url
                    // --------------------------------------
                } 

            }
            catch (exception) {
                console.error("[window onload] : [Exception] : 예외 상황 발생 : ", exception);
            }
        };





        // [Create : Mqtt Object]
        function mqttConnect(url) {
            console.log("[mqttConnect] : [start] : ", url);

            try {

                // --------------------------------------
                // Clear : Subscribe Topic List
                // --------------------------------------
                subscribeTopicList.length = 0;
                

                // --------------------------------------
                // Set : Client Id
                // --------------------------------------
                const clientId = "clientId-" + Math.random();


                // --------------------------------------
                // Connect : mqttClient
                // --------------------------------------
                console.log("");
                console.log("=========================================");
                console.log("[Mqtt] : connect : start : ", clientId);
                console.log("=========================================");
                console.log("");

                mqttClient = mqtt.connect(url, { // Custom Option
                        
                    clientId: clientId, // 클라이언트 아이디
                    protocolVersion: 5, // ✅ 4(MQTT 3.1.1) 또는 5(MQTT 5)
                    connectTimeout: (CONNECT_TIME_OUT * 1000), // CONNACK 대기 타임아웃(ms, 기본 30000)
                    keepalive: 30, // ✅ Ping 주기 (초) → 기본값은 60초
                    clean: true, // 이전 세션 초기화
                    resubscribe: false, // 재연결 시 구독 자동 복원(기본 true)
                    reconnectPeriod: 0, // 0 값은 자동 재연결 비활성화
                });


                // --------------------------------------
                // ❌ Connect : TimeOut Handler
                // --------------------------------------
                connectTimer = setTimeout(() => {
                    console.error("");
                    console.error("=========================================");
                    console.error("[Mqtt] : connect : TimeOut");
                    console.error("=========================================");
                    console.error("");

                    try {
                        if (mqttClient != null){
                            mqttClient.end();
                            mqttClient = null;
                        }

                        onConnectionLost();
                    }
                    catch (exception) {
                        
                        try { mqttClient = null; } catch (error){ }
                        try { onConnectionLost(); } catch (error){ }
                                                
                    }                    

                }, (CONNECT_TIME_OUT * 1000) );


                // --------------------------------------
                // ✅ Connect : CallBack Handler
                // --------------------------------------                
                mqttClient.on('connect', (connack) => {
                    console.warn('[Mqtt] : Connected : ', connack);
                    
                    onConnect(connack);
                });

                mqttClient.on('reconnect', () => { // 자동 재연결 시도가 시작될 때 호출됩니다 (reconnectPeriod 속성 값 > 0 일때)
                    console.warn('[Mqtt] : Reconnecting...');
                });

                mqttClient.on('close', () => { // 연결이 완전히 닫혔을 때(소켓 종료) (onConnectionLost 와 가장 가까운 타이밍)
                    console.error('[Mqtt] : Connection closed');

                    onConnectionLost('Connection closed');
                });

                mqttClient.on('offline', () => { // 네트워크 단절 등으로 클라이언트가 오프라인 상태가 되었을 때. (재연결 로직이 계속 동작할 수 있음)
                    console.error('[Mqtt] : Client is offline');

                    onConnectionLost('Client is offline');
                });

                mqttClient.on('error', (err) => { // 연결/인증/프로토콜 오류가 발생했을 때 호출됩니다. 초기 연결 실패 (예: 인증 오류로 CONNACK=Error)도 여기로 들어옵니다.
                    console.error('[Mqtt] Connection error : ', err);

                    onConnectionLost(err.message);
                });

                mqttClient.on('disconnect', (packet) => { // 브로커가 DISCONNECT 패킷을 보내온 경우(이유 코드 포함)
                    console.error('[Mqtt] Disconnected by broker : ', packet);

                    onConnectionLost('Disconnected by broker');
                });

                mqttClient.on('message', (topic, payload, packet) => {
                    console.warn(`[Mqtt] Message on : topic = ${topic} : payload = `, payload.toString(), ' : packet = ', JSON.stringify(packet));

                    onMessageArrived(topic, payload.toString());
                });

            }
            catch (exception) {
                console.error("[mqttConnect] : [Exception] : 예외 상황 발생 : ", exception);
            }
        }





        // [Connection : Callback Handler]
        function onConnect(connack) {

            // --------------------------------------
            // Check : Mqtt Client
            // --------------------------------------
            if (mqttClient == null){
                return;
            }

            console.log("");
            console.log("=========================================");
            console.log("[Mqtt] : connect : onSuccess");
            console.log("-----------------------------------------");
            console.log(JSON.stringify(connack));
            console.log("=========================================");
            console.log("");


            // --------------------------------------
            // Clear : Connect TimeOut Timer
            // --------------------------------------
            if (connectTimer != null){
                clearTimeout(connectTimer);
                connectTimer = null;
            }


            // --------------------------------------
            // Logic : Procedure

            // --------------------------------------
            try {

                // ---------------------------------
                startConnectionCheck(); // ✅ MQTT Connect State Check                
                // ---------------------------------
                // ✅ [Test] : AWS : Subscribe
                // ---------------------------------
                onSubscribe(publish_topic_common); // ✅ MQTT5 사용자 속성 확인 구독 : publish 발행한 토픽과 같은 걸 구독해야 사용자 속성 값 확인 가능
                // ---------------------------------
                // ✅ [Test] : AWS : Publish
                // ---------------------------------
                //onPublish(publish_topic_common, JSON.stringify(publish_json_empty)); // ✅ MQTT5 사용자 속성 전송 메시지 : publish 수행 시 properties 를 추가해서 전송 필요
                // ---------------------------------

            }
            catch (exception) {
                console.error("[onConnect] : [Exception] : 예외 상황 발생 : ", exception);
            }
            
        }





        // [Connection : Callback Handler]
        function onConnectionLost(params) {
            console.error("[Mqtt] : connect : onConnectionLost : ", params);


            // --------------------------------------
            // Clear : Subscribe Topic List
            // --------------------------------------
            if (subscribeTopicList != null){
                subscribeTopicList.length = 0;
                console.error("[Mqtt] : onConnectionLost : Clear : subscribeTopicList Array");
            }            


            // --------------------------------------
            // Clear : Connect TimeOut Timer
            // --------------------------------------
            if (connectTimer != null){
                clearTimeout(connectTimer);
                connectTimer = null;
                console.error("[Mqtt] : onConnectionLost : Clear : Connect TimeOut Timer");
            }


            // --------------------------------------
            // Clear : Ping Timer
            // --------------------------------------
            if (checkTimerId) {
                clearInterval(checkTimerId);
                checkTimerId = null;
                checkCnt = 1;            
                console.error("[Mqtt] : onConnectionLost : Clear : Ping Timer");
            }


            // --------------------------------------
            // Clear : Mqtt Object
            // --------------------------------------
            try {
                if (mqttClient != null){
                    mqttClient.end(); 
                    mqttClient = null;
                }                        
            }
            catch (exception) { // disconnect error
                console.error("[Mqtt] : onConnectionLost : disconnect : Exception : ", exception.message);  
                mqttClient = null;              
            }
            console.error("[Mqtt] : onConnectionLost : Clear : mqttClient connect");
        }






        
        // [Connection : Check]
        var checkTimerId = null;
        var checkCnt = 1;
        function startConnectionCheck() {
            if (checkTimerId) return; // 중복 방지
            
            // --------------------------------------
            // Logic : Procedure
            // --------------------------------------
            try {
                checkTimerId = setInterval(() => {
                
                    if (mqttClient != null && mqttClient.connected === true) {                    
                        console.log("Mqtt 연결 정상 ✅ : Count : ", checkCnt);

                        checkCnt ++; // Plus Count

                    } else {
                        console.error("Mqtt 연결 끊김 ❌ → 타이머 종료 : Count : ", checkCnt);
                        
                        if (checkTimerId) {
                            clearInterval(checkTimerId);
                            checkTimerId = null;
                            checkCnt = 1;                            
                        }
                    }

                }, (CONNECT_CHECK_TIME * 1000) ); // 지정 시간 마다 체크                

                console.log("Mqtt 연결 상태 체크 타이머 동작 ✅ : 체크 주기 : Seconds : ", CONNECT_CHECK_TIME);
                checkCnt = 1; // Set Init
            }
            catch (exception) {
                console.error("[startConnectionCheck] : [Exception] : 예외 상황 발생 : ", exception);

                
                if (checkTimerId) {
                    clearInterval(checkTimerId);
                    checkTimerId = null;
                    checkCnt = 1;
                    console.error("[startConnectionCheck] : [Exception] : 연결 상태 체크 타이머 종료");
                }

            }

        }






        // [Topic : Subscribe]
        function onSubscribe(topic) {

            // --------------------------------------
            // Check : Mqtt Client
            // --------------------------------------
            if (mqttClient == null){
                return;
            }

            console.log("[Mqtt] : onSubscribe : Start : ", topic);


            // --------------------------------------
            // Logic : Procedure
            // --------------------------------------
            try {

                // ---------------------------------------------
                // ✅ MQTT 5 속성 값 확인을 위한 구독 토픽 지정 설명
                // ---------------------------------------------
                // Shadow 서비스가 발행하는 응답 (예: …/update/accepted) 에서는 사용자 publish 수행한 사용자 속성 값을 확인할 수없습니다
                // ---------------------------------------------
                // 사용자 속성을 확인하려면 “내가 발행한 토픽”을 구독해서 수신해야 합니다 (즉, publish 수행한 토픽을 구독하고 있어야 사용자 속성 확인 가능)
                // ---------------------------------------------
                
                mqttClient.subscribe(topic, { // Custom Set Topic Subscribe

                    qos: QOS, // 네트워크 서비스 품질                    

                }, (err, granted) => {

                    if (err) {
                        console.error("[Mqtt] : onSubscribe : Error : ", err);
                    }
                    else {
                        console.log("[Mqtt] : onSubscribe : Granted : ", JSON.stringify(granted));

                        if (granted == null || String(typeof granted).toLowerCase() == "undefined" || JSON.stringify(granted) == "null") {
                            console.error("[Mqtt] : onSubscribe : Error : Callback Granted Is Null");
                        }
                        else {
                            try {
                                const grantTopic = JSON.stringify(granted);                            

                                if (subscribeTopicList.indexOf(grantTopic) < 0){ // 배열에 특정 값을 포함하지 않는 경우
                                    subscribeTopicList.push(grantTopic);

                                    console.warn("[Subscribe Topic List Total Count] : ", subscribeTopicList.length);
                                }
                            }
                            catch (exception) {
                                console.error("[onSubscribe] : [Topic List Add] : 예외 상황 발생 : ", exception.message);
                            }
                        }
                    }
                });

            }
            catch (exception) {
                console.error("[onSubscribe] : [Exception] : 예외 상황 발생 : ", exception);
            }
        }






        // [Topic : Publish]
        function onPublish(topic, message) {

            // --------------------------------------
            // Check : Mqtt Client
            // --------------------------------------
            if (mqttClient == null){
                return;
            }

            console.log("[Mqtt] : onPublish : Start : ", topic);


            // --------------------------------------
            // Logic : Procedure
            // --------------------------------------
            try {

                
                // ✅ MQTT5 속성 값 지정 : userProperties 생성
                const userProps = {
                    source: String('webapp'),
                    traceId: String('TWOK1234')
                };


                mqttClient.publish(topic, message, { // Custom Set publish

                    qos: QOS, // 네트워크 서비스 품질

                    retain: false, // 메시지를 브로커에 보관하지 않음

                    properties: { // ✅ MQTT5 속성 값 지정 : 사용자 속성 전송 시 구독하고 있는 곳에서도 사용자 속성으로 구독을 해야함                                       
                        messageExpiryInterval: 60, // 메시지 유효기간 : 60초 후 만료 > 브로커에서 삭제
                        userProperties: userProps, // MQTT5 사용자 속성 : { [key]: string } 값은 문자열                        
                    }

                }, (err) => {

                    if (err) {
                        console.error("[Mqtt] : onPublish : Error :: ", err);
                    }
                    else {
                        console.log("[Mqtt] : onPublish : Success :: ", topic);
                    }
                });                

            }
            catch (exception) {
                console.error("[onPublish] : [Exception] : 예외 상황 발생 : ", exception);
            }
        }





        // [Receive : Message]
        function onMessageArrived(topic, message) {

            // --------------------------------------
            // Check : Mqtt Client
            // --------------------------------------
            if (mqttClient == null){
                return;
            }

            var timeStamp = String(Date.now()); // 타임스탬프
            var date = dayjs(Number(timeStamp));
            var nowDate = date.format("YYYY-MM-DD HH:mm:ss");

            console.log("");
            console.log("=========================================");
            console.log("[Mqtt] : receive : onMessageArrived");
            console.log("---------------------------------------");
            console.log("[nowDate] : ", nowDate);
            console.log("---------------------------------------");
            console.log("[topic] : ", topic);
            console.log("---------------------------------------");
            console.log("[payloadString] : ", message);
            console.log("=========================================");
            console.log("");
        }

        // --------------------------------------------------------------------------------------------------------------

    </script>


</head>


<body>

</body>

</html>

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[참고 사이트]
-----------------------------------------------------------------------------------------

MQTT (Message Queueing Telemetry Transport) 통신 설명

https://kkh0977.tistory.com/3631

https://blog.naver.com/kkh0977/222971771381?trackingCode=blog_bloghome_searchlist


[라이브러리] [Android] paho.mqtt.android - MqttAndroidClient 안드로이드 MQTT 통신 라이브러리

https://blog.naver.com/kkh0977/223557263865?trackingCode=blog_bloghome_searchlist


[MQTT] mosquitto 사용해 MQTT 통신 테스트 환경 구축

https://blog.naver.com/kkh0977/222971768497?trackingCode=blog_bloghome_searchlist


[mqtt] 온라인 MQTT 테스트 수행 참고 사이트

https://blog.naver.com/kkh0977/223557261164?trackingCode=blog_bloghome_searchlist

-----------------------------------------------------------------------------------------
 
728x90
반응형
Comments