Research notes



연구 노트 2025_0112_0208_SPRT_03_06 RX004 01조

개요

센서로 수집한 온도와 습도 데이터를 게이트웨이를 통해 MQTT메세지로 전달하여 lambda function으로 가공하고 DynamoDB에 저장한다.


01월 12일 - 01월 18일 (01주차)

목표

  1. rak 1901 센서 보드에 장착하고 동작여부 확인하기

  2. rak 7248 게이트웨이 aws iot core에 등록하기


참고 링크: RAK 7248 guide , RAK 4631 guide , RAK 1901 guide


과정

• rak 1901 동작 테스트
  • rak 1901 설정
    1. rak 1901을 사용하기 위해서는 기본적으로 아두이노 IDE가 설치되어 있어야 한다.(윈도우 10 이상을 사용하고 있다면 윈도우 스토어에서 다운받으면 안 되고 공식 웹사이트에서 다운 받아야 한다.)

    2. 아두이노를 설치하였으면 adding the BSP in Arduino IDE 를 들어가 기본 설정을 마친다.

    3. RAK 1901 guide 의 설명을 따라 센서를 보드에 부착하고 아두이노 가이드도 그대로 따라하면 문제없이 동작한다.

• 게이트웨이 등록하기
  • 게이트웨이 설정
    1. RAK 7248 guide 링크를 들어가서 먼저 datasheet 항목에 들어간다. 그리고 아래롤 스크롤하면 어느 위치에 안테나를 꽂아야 하는지 그림이 나와 있다. 그림을 참고해서 안테나를 꽂는다.

    2. 다음으로 quick start guide 항목에 들어간다. 가이드에 SD카드를 이용해서 펌웨어 설치를 하는 과정이 있지만 이 과정은 이미 되어 있어서 바로 다음 과정으로 넘어간다. 자신이 wifi를 사용할지 이더넷 케이블을 사용할지 정한 다음 각가의 가이드를 따라서 수행하면 문제없이 완료된다. 이때 optional configuration 은 수행할 필요없다. 이 과정을 완료하면 게이트웨이에 접속하여 로그인까지 가능하다.

    이 다음으로 LoRaWAN Network Server Guide를 해야하는데 게이트웨이에 문제가 생겨서 더이상 진행하지 못 했다.

결과

  1. rak 1901 센서 보드에 장착하고 동작여부 확인하기

    –> 보드에 장착하고 동작하는것을 확인했음

  2. rak 7248 게이트웨이 aws iot core에 등록하기

    –> 게이트웨이 로그인은 가능했지만 iot core에 등록은 실패


01월 19일 - 01월 25일 (02주차)

목표

  1. rak 7248 게이트웨이 aws iot core에 등록하기

  2. rak 4631 디바이스 aws iot core에 등록하기


참고 링크: RAK 7248 guide , RAK 4631 guide , RAK 7268 guide


과정

• 게이트웨이 설정

시작하기 앞서 게이트웨이를 iot core에 등록하려면 deviceEUI가 필요한데 무슨 이유인지 가지고 있던 게이트웨이에 deviceEUI가 들어가 있지 않아서 교수님의 도움으로 deviceEUI를 강제로 등록했다. 그리고 aws계정을 생성해야하는데 계정을 생성하고 예산설정 을 해줘야 지나친 요금 청구를 막을 수 있다.

  • 게이트웨이를 AWS IoT Core에 등록
    1. RAK 7248 guide 에서 LoRaWAN Network Server Guide에 들어간다. 먼저 게이트웨이에 basic staion을 설치해야한다. basic station 설치 가이드는 LoRa Basic Station 의 Quick Start 항목을 그대로 따라하면 된다. 다른 항목은 안 해도 지금 작업에는 문제 없어서 하지 않았다.

    2. 게이트웨이를 iot core에 등록하려면 인증서가 필요하다. 인증서 발급을 위해서 aws의 online guide 에 들어가서 AWS IoT Core for LoRaWAN - Connecting to AWS IoT Core for LoRaWAN - Onboard your gateways to AWS IoT Core for LoRaWAN - Add a gateway to AWS IoT Core for LoRaWA항목에 들어간다. (주의 : online guide에서 제공하는 게에트웨이 등록방법을 따라하면 안 된다. online guide의 내용은 기본적으로로 RAK 7248 guide 의 가이드를 따라가면서 필요할 때에만 참고해야한다.) 우리는 처음 등록하는 것이므로 Add a gateway using the console 방법을 따라한다. 이 방법을 수행했으면 다른 방법은 따라하지 않아도 된다.

    3. LoRa 게이트웨이는 Configuration and Update Service Protocol(CUPS)프로토콜을 사용하는데 이 프로토콜이 게이트웨이 인증서를 관리하기위해서는 IAM role을 추가해줘야한다. 이를 수행하기 위해 online guide 에 들어가서 Select frequency band and add IAM role항목에 들어가서 가이드를 따라하면 된다.

    4. RAK7248 LoRaWAN Network Server Guide에서 스크롤을 하면 Configure the Gateway Device 항목이 보이는데 여기서부터 진행하면 된다. putty를 통해 게이트웨이에 로그인 하고 가이드에 있는 명령어를 한 줄씩 입력하면 된다. 이때 파일을 수정해야할 필요가 있는데 <nano ‘수정할 파일명’>을 입력하면 편집기가 열린다. 수정한 다음에 Ctrl+x를 눌러 저정한다. 수정된 파일의 내용을 보려면 <cat ‘파일명’>을 입력하면 편집기를 열지 않아도 바로 볼 수 있다. 또한 11번 단계에서 이전에 만든 인증서를 게이트웨이의 lns-aws폴더에 추가해야하는데 추가할 때 인증서 형식을 정확히 맞춰서 넣어야 오류가 발생하지 않는다.

    5. 4번 과정을 모두 끝내고 게이트웨이에서 basic station을 실행하면 aws iot core에서 게이트웨이의 status가 connected로 되어있는 것을 확인할 수 있다.

• 디바이스 등록

디바이스를 iot core에 추가하고 연결 테스트를 할려면 아두이노와 aws에서 설정을 해줘야 한다.

  • 아두이노 설정
    1. RAK 4631 guide 에 들어가서 Quick Start Guide 항목에 들어간다. 그리고 스크롤을 내리면 Connecting RAK4631 to LoRaWAN이 보인다. 이제 이 과정을 따라하면 되는데 region은 KR920, 5번 설정의 nodeDeviceEUI[8], nodeAppEUI[8], nodeAppkey[8]를 본인인 하고싶은 값으로 설정하고 기억해둔다. 그 외의 나머지 항목은 수정하지 않는다.

    2. 코드 수정이 완료되었으면 스크롤을 내려 Uploading the LoRaWAN Code 항목을 찾아서 설치하라는 라이브러리를 설치하면 아두이노에서 필요한 작업은 끝난다.

  • aws 설정
    1. RAK 7268 guide 들어가서 LNS Guide 항목에 들어간다.

    2. 스크롤을 내려 Add a LoRaWAN Device to AWS IoT 항목을 찾아서 가이드 대로 따라한다. 3번, 4번은 가이드를 따라하면서 특별히 설정해줘야 사항들이다.

    3. Device Profile을 추가할 때, MAC version은 1.0.2로 설정한다.

    4. Add Device - Configure LoRaWAN device에서 OTAA v1.0.x 선택, DevEUI, Appkey, AppEUI는 아두이노에서 본인이 설정한 값을 입력한다.

    5. Add Device까지 마무리 했다면 스크롤을 내려 Verifying Operation 항목을 찾아서 lambda function과 Destination Rule을 추가한다.

위의 두 가지 영역에서의 설정을 모두 마쳤으면 연결 테스트를 할 수 있다.

  • 연결 테스트
    1. 게이트웨이에서 basic station을 실행한다.

    cd basicstation/examples/corecell/
    
    ./start-station.sh —l ./lns-aws
    
    1. 아두이노 코드를 컴파일 하고 업로드를 한다.

    2. iot core에 들어가 Test - MQTT test Client에 들어간다.

    3. topic filter에 #을 입력하고 구독한다.

    4. payloadData에 base64로 인코딩된 메세지가 도착한다.

    5. 디코딩 해보면 hello!가 나온다.

결과

  1. rak 7248 게이트웨이 aws iot core에 등록하기

    –> 성공

  2. rak 4631 디바이스 aws iot core에 등록하기

    –> 성공, MQTT 메세지 보내기도 성공


01월 26일 - 02월 01일 (설 연휴)

재충전


02월 02일 - 02월 08일 (03주차)

목표

  1. DynamoDB Tutorial

  2. Lambda Tutorial

  3. device에서 온도 습도 읽어서 Aws IOT core로 보내고 Lambda fuction 사용하여 DB에 저장


참고 링크: RAK7248 가이드, RAK7268 가이드, RAK1901 가이드, DynamoDB 문서 , Lambda 문서


과정

  • Tutorial: Storing device data in a DynamoDB table

    • DynamoDB 문서 를 그대로 진행한다.

    • 진행 중 주의사항

      1. IMA role 로 만들어낸 role의 policy에 AmazonDynamoDBFullAccess 집어넣으니 진행 되었다. role 생성시 policy가 자동으로 생성 되지 않는 경우도 있으니 주의한다.

      2. DynamoDB의 table 생성 방법과, IAM rule, IAM policy 사용 방법을 숙지해둔다.

  • Tutorial: Formatting a notification by using an AWS Lambda function

    • Lambda 문서 를 그대로 진행한다.

    • 진행 중 주의사항

      1. 위의 튜토리얼을 진행하기 전에 해야할 사이트가 먼저 존재한다.

      2. 위 문서에서 Tutorial: Sending an Amazon SNS notification 를 찾아 먼저 적용해보되 전화번호로 하면 지출이 발생하므로 email로 진행한다.

      3. mqtt에서 Publish to a topic에서 ID를 만들어주고 publish 해주면 Subscribe to a topic으로도 같은 내용 전달되고 적용한 이메일로도 같은 내용이 전달된다.

    • 이제 다시 Lambda 문서 lamda function으로 돌아가서 진행하자.
      1. 람다 펑션에서 test로 보내기를 해볼건데 iot core에서 rules 만들 때 action에 Amazon SNS-Topics-high_temp_notice_ARN 의 ARN을 action에 추가해준다. 그럼 rules의 action을 통해 람다 함수가 실행된뒤 다시 iot core로 test를 보내준다.

      2. action은 이후에도 사용할 예정이니 잘 숙지해둔다.

  • device에서 온도 습도 읽어서 Aws IOT core로 보내고 MQTT로 확인

    1. RAK7248 가이드RAK7268 가이드 를 참고하여 device의 destination 설정을 완료하고 Message routing-Rules-action-role: IAMrole/LoRaWANRougint 까지 진행 해준다.

    2. 이후 device에서 Aws IOT core 까지 전송된 data 처리를 하기 위해 필요한 과정 중 하나인 LoRaWANRouting에 action으로 MQTT test client로 device에서 전송된 데이터를 주고받기위해 이를 인식할 수 있는 Topic을 가진 Message routing_Rules를 만들었다. 이후 여기에 넣을 IAM_role을 추가하기 위한 IAM_Policies를 추가해줬다. policies의 JSON 코드를 고쳐서 수신가능하게 넣어줘야한다. 그렇지 않으면 device의 destination에 device의 정보가 도착해도 그 어떠한 일도 일어나지 않는다. 그렇기에 rule인 LoRaWANRouting에 action으로 정보를 ‘어떻게’ 할 것인지 결정해줘야 한다. MQTT로 정보를 전달하는 JSON을 policy에 직접 수동으로 주가해서 rule에 집어넣어도 되고 아니면 이미 만들어져 있는 policy를 그대로 rule에 추가해줘도 된다.

    3. device에서 destinaitin 설정을 할때 rules 에서 LoRaWANRouting을 만들텐데 RAK7268 가이드 에서 Ctrl+F 를 눌러 ‘LoRaWANRouting’을 찾고 2번째 글을 참고해야한다. 둘이 떨어져 있어 자칫하면 설정이 누락될 수 있으니 주의한다.

    4. 온습도 예제를 RAK1901 가이드 에서 찾아 가이드를 따라 진행해준다. MQTT로 전송이 되는 걸 확인하면 성공이다.

    5. topic:project/sensor/decoded로 전송 될 것이다. 참고로 어떤 topic으로 전송되고 받는지는 IAM policy의 JSON 혹은 Message routing-Rules에서 처음 rule을 생성할때, Lambdafunction을 사용해서 모두 가능하다.

  • device에서 온도 습도 읽어서 Aws IOT core로 보내고 Lambda fuction 사용하여 DB에 저장

    • 전체 과정
      1. device data– 아두이노 코드 – aws 서버 – MQTT 서버로 이동

      2. AWS IoT/Message routing/Rules/LoRaWANRouting(device의 destination에 있음) destination에서 어떤 역할을 할지 정해주는게 추가할 action

      3. LoRaWANRouting의 action1 에 Republish to AWS IoT topic 설정

      4. mqtt 서버(topic:project/sensor/decoded) 보냄

      5. LoRaWANRouting의 action2 에 Lambdafunction(role(policy)) 작동, 이후 MQTT로 data 받으면 디코딩해서 topic:project/sensor/output으로 송신

      6. DB table로 rule인 send_to_DB_rule(role(policy(topic:project/sensor/output 내용을 받아서 db로 자동으로 보냄)))

      7. DB table에 저장되는 것을 DynamoDB consol 에서 확인 할 수 있다.

    • 추가 사항 Aduino
      1. 아두이노에서 기본적으로 제공되는 온습도 예제와 전송 코드를 사용해야한다.

      2. 도구/보드:WisBlock RAK4631/RAKwireless nRF Boards/WisBlock RAK4631 보드 선택

      3. 파일/예제/RAK WisBlock examples/RAK4631/Communications/LoRa/LoRaWAN/LoRaWAN_OTAA_ABP 송신 예제를 열어준다.

      4. 파일/예제/RAK WisBlock examples/RAK4631/Sensors/RAK 1901_Temperatiure_Humidity_SHTC3 온습도 예제를 열어준다.

      5. 코드를 사용하여 적절히 만들어준다.

      void send_lora_frame(void)
      {
        if (lmh_join_status_get() != LMH_SET)
        {
          // Not joined, try again later
          return;
        }
      
        memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
        m_lora_app_data.port = gAppPort;
        uint32_t i = 0;
      
        //온도 데이터 전송
        float temp = g_shtc3.toDegC();
        char tem[10];
        sprintf(tem,"%.2f",temp);
        for (uint8_t k = 0; tem[k] != '\0'; k++)
        {
          m_lora_app_data.buffer[i++] = tem[k];  // 문자 한 개씩 전송
        }
        //구분자
        m_lora_app_data.buffer[i++] = '|';
        //습도 데이터 전송
        float humi = g_shtc3.toPercent();
        char str[10];
        sprintf(str,"%.2f",humi);
        for (uint8_t k = 0; str[k] != '\0'; k++)
        {
          m_lora_app_data.buffer[i++] = str[k];  // 문자 한 개씩 전송
        }
      
        m_lora_app_data.buffsize = i;  // 데이터 크기 설정
      }
      
    • 추가 사항 Lambda
      1. Aws IOT core consol에서 Manage/LPWAN devices/Destinations 설정과, Message routing/Rules/LoRaWANRouting 설정이 다 되어 있는 상태여야한다.

      2. LoRaWANRouting에 새로운 action2 를 만들어주고 이에 해당되는 Lambdafunction을 만들어준다. Lambdafunction을 미리 만들어도 되고 나중에 추가해줘도 된다. Bluprint 설정과 Hello에 python을 설정해주는것을 잊지말자.

      3. Lambdafunction을 만들었으면 rule을 추가해줘야한다. rule에는 role이 들어가고 role에는 DynamoDB로 전송해주는 Policy를 추가해줘야한다. IAM role의 roles와 policies에서 새롭게 만들거나 기존에 동일한 기능을 하는 것이 있는 경우 같은 것을 사용해도 무방하다.

      4. Lambdafunction에 코드를 추가해줘야한다. 코드는 원하는 것을 사용해도 되나 여기선 python을 사용한다.

      5. 우리는 아두이노에서 MQTT로 송신하는 data를 받아서 Lambda에서 가공할 것이다. 다만 아두이노에서 전송할때 숫자형으로 보내는 것이 안 되어 자료형으로 바꾸어 전송했고 구분자를 두어 온도, 습도를 구분했다.

      6. 코드 예시는 다음과 같다.

      # PayloadData 디코딩
      if "PayloadData" in event:
          decoded_payload = base64.b64decode(event["PayloadData"]).decode("utf-8")
          print(f"Decoded Payload: {decoded_payload}")
      
          # temperature랑 humidity 분리
            if "|" in decoded_payload:  # 구분자가 있을 때만 분리
                temp, humi = decoded_payload.split("|", 1)  # '|' 구분자로 분리
             print(f"temp: {temp}, humi: {humi}")
         else:
                # 혹시라도 안 될때 긴급조치
             temp = decoded_payload[:4]  # 숫자 "10"
             humi = decoded_payload[4:]  # 나머지 문자열 ":humi"
      
         # humidity 뒤에 "%" 추가
         humi = humi + " %"  # "%"
            # temp 뒤에 "def C" 추가
            temp = temp + " deg C"  # "deg C"
      
    • 추가 사항 DynamoDB
      1. DynamoDB consol에서 table 을 만들어준다.

      2. 여기서는 Lambdafunction 에서 바로 가공한 data를 보내는 것이 아니라 다시 특정 topic MQTT로 전송된 데이터를 IOT core가 다시 DB table로 보낼 수 있게 해준다.

      3. 따라서 IOT core 의 Message routing/Rules 에 새로운 rule 을 추가하여 특정 topic으로 data를 받은 IOT core가 지저오딘 DynamoDB의 table로 data를 저장할 수 있게 해준다.

      4. rule에 들어갈 SQL구문에 rule이 받는 Topic을 FROM으로 지정해주고, action에 DynamoDB를 추가하여 IOT core가 data를 전송 할 수 있게 한다. 예시: (SELECT temp, humi FROM ‘project/sensor/output’)

      5. rule에 들어갈 Policy는 이미 AWS에서 제공하고 있으므로 aws-iot-rule-send_to_DB_rule-action-1-role-send_to_DB_rule_role을 그대로 사용하면 된다.

      6. 정상적으로 설정이 완료되었다면 Aduino code에 따라 DynamoDB의 특정 table로 데이터가 저장되는 것을 확인할 수 있다.

결과

  1. DynamoDB Tutorial

    → 성공

  2. Lambda Tutorial

    → 성공

  3. device에서 온도 습도 읽어서 Aws IOT core로 보내고 Lambda fuction 사용하여 DB에 저장

    → 성공

  4. device에서 온도 습도 읽어서 Aws IOT core로 보내고 Lambda fuction 사용하여 DB에 저장

    → 성공


결산

  1. 단말장치 개발
    • 설정된 주기로 온도 습도 데이터를 수집해 서버로 전송
    –> rak 4630, rak 1901을 사용하여 개발 완료
  2. 게이트웨이 개발
    • 단말장치가 전송하는 데이터를 MQTT 프로토콜을 사용해 서버로 전달
    –> rak 7248을 사용하여 개발 완료
  3. 서버 개발
    • 단말장치들이 전송하는 데이터를 받아서 데이터베이스에 저장
    • 저장된 데이터 그래프로 시각화
    –> lambda function과 DynamoDB를 사용해 데이터 저장기능 개발 완료
    –> 그래프로 시각화는 미구현

기타



연구 노트 2025_0112_0208_SPRT_03_06 RX004 02조

개요

AWS IOT Core에 RAK4630을 디바이스로 등록하고 RAK7248을 게이트웨이로 등록하여 LoRaWAN으로 연결한다. RAK4630에서 보낸 온, 습도 값을 MQTT로 받아 Lambda함수로 보낸다. Lambda함수에서 값을 뽑아 DynamoDB로 보낸 후 데이터를 쌓는다.


01월 12일 - 01월 18일 (01주차)

RAK11310 사전 준비

RAK11310

1.아두이노 IDE를 다운받는다.

2.RAK1901을 RAK11310에 부착한다.

3.Arduino IDE를 연다. 환경 설정으로 간다.

4.Arduino Boards 목록에 WisBlock Core를 추가하려면 추가 Board Manager URL을 편집해야 한다.

5.이 URL을 복사하여 https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json그림 에 표시된 대로 필드에 붙여넣는다. 다른 URL이 이미 있는 경우 다음 줄에 추가하기만 하면 된다. URL을 추가한 후 확인을 클릭한다.

그림 55 URL추가하는 부분

URL추가

그림 55 URL추가

6.Arduino IDE를 다시 시작한다. 도구 메뉴에서 보드 관리자를 연다.

7.그러면 Arduino 보드 목록에 추가할 수 있는 사용 가능한 RAKwireless WisBlock Core 보드가 표시된다. 보유한 WisBlock Core를 선택하고 드롭다운 목록에서 업데이트된 버전을 선택한 다음 설치를 클릭한다. (RAKwireless RP Boards)

RAK1901

1.먼저, 자신이 가지고 있는 WisBlock Core를 선택해야 한다. (RAK11310)

2.SparkFun SHTC3 라이브러리를 설치한다 .

3.올바른 직렬 포트를 선택하고 코드를 업로드할 수 있다.

#include "SparkFun_SHTC3.h"       //Click here to get the library: http://librarymanager/All#SparkFun_SHTC3

SHTC3 g_shtc3;                        // Declare an instance of the SHTC3 class

void errorDecoder(SHTC3_Status_TypeDef message)   // The errorDecoder function prints "SHTC3_Status_TypeDef" resultsin a human-friendly way
{
switch (message)
{
    case SHTC3_Status_Nominal:
    Serial.print("Nominal");
    break;
    case SHTC3_Status_Error:
    Serial.print("Error");
    break;
    case SHTC3_Status_CRC_Fail:
    Serial.print("CRC Fail");
    break;
    default:
    Serial.print("Unknown return code");
    break;
}
}

void shtc3_read_data(void)
{
    float Temperature = 0;
    float Humidity = 0;

    g_shtc3.update();
    if (g_shtc3.lastStatus == SHTC3_Status_Nominal) // You can also assess the status of the last command by checking the ".lastStatus" member of the object
    {

        Temperature = g_shtc3.toDegC();                   // Packing LoRa data
        Humidity = g_shtc3.toPercent();

        Serial.print("RH = ");
        Serial.print(g_shtc3.toPercent());                // "toPercent" returns the percent humidity as a floating point number
        Serial.print("% (checksum: ");

        if (g_shtc3.passRHcrc)                               // Like "passIDcrc" this is true when the RH value is valid from the sensor (but not necessarily up-to-date in terms of time)
        {
            Serial.print("pass");
        }
        else
        {
            Serial.print("fail");
        }

        Serial.print("), T = ");
        Serial.print(g_shtc3.toDegC());                  // "toDegF" and "toDegC" return the temperature as a flaoting point number in deg F and deg C respectively
        Serial.print(" deg C (checksum: ");

        if (g_shtc3.passTcrc)                               // Like "passIDcrc" this is true when the T value is valid from the sensor (but not necessarily up-to-date in terms of time)
        {
            Serial.print("pass");
        }
        else
        {
            Serial.print("fail");
        }
        Serial.println(")");
    }
    else
    {
    Serial.print("Update failed, error: ");
        errorDecoder(g_shtc3.lastStatus);
        Serial.println();
    }
}

void setup()
{
    time_t timeout = millis();
    Serial.begin(115200);
    while (!Serial)
    {
        if ((millis() - timeout) < 5000)
        {
            delay(100);
        }
        else
        {
            break;
        }
    }

    Wire.begin();
    Serial.println("shtc3 init");
    Serial.print("Beginning sensor. Result = "); // Most SHTC3 functions return a variable of the type "SHTC3_Status_TypeDef" to indicate the status of their execution
    errorDecoder(g_shtc3.begin());              // To start the sensor you must call "begin()", the default settings use Wire (default Arduino I2C port)
    Wire.setClock(400000);                            // The sensor is listed to work up to 1 MHz I2C speed, but the I2C clock speed is global for all sensors on that bus so using 400kHz or 100kHz is recommended
    Serial.println();

    if (g_shtc3.passIDcrc)                      // Whenever data is received the associated checksum is calculated and verified so you can be sure the data is true
    {                                                        // The checksum pass indicators are: passIDcrc, passRHcrc, and passTcrc for the ID, RH, and T readings respectively
        Serial.print("ID Passed Checksum. ");
        Serial.print("Device ID: 0b");
        Serial.println(g_shtc3.ID, BIN);             // The 16-bit device ID can be accessed as a member variable of the object
    }
    else
    {
        Serial.println("ID Checksum Failed. ");
    }
}

void loop()
{
    shtc3_read_data();
    delay(1000);
}

그림 56 온,습도 예제 업로드 결과​

실행결과

그림 56 실행결과

RAK7248 사전 준비

RAK7248

1.RAK7248의 초기 설정은 AP모드 이므로 노트북으로 게이트웨이에 putty에 RAK7248의 기본 ip인 192.168.230.1로 접근 한다.

2.이후 사용자 이름: pi 비밀번호:raspberry를 입력하여 로그인 한 후, sudo gateway-config를 입력하여 게이트웨이 설정으로 이동한다.

3.이후 거주 국가를 선택한다. ap모드를 해제한 후 클라이언트 모드로 변경한다. 이후 연결할 와이파이 SSID와 비밀번호를 입력한다.

4.이제 와이파이에 연결된 RAK7248의 ip주소를 putty에 입력하면 콘솔에 접근할 수 있다.


01월 19일 - 01월 25일 (02주차)

DynamoDB 콘솔

DynamoDB 자습서 를 수행하여 DynamoDB가 무엇인지와 사용하는 방법을 공부하였다.

DynamoDB 테이블 생성

AWS IoT Core에서 데이터를 저장할 DynamoDB 테이블을 생성한다.

1.dynamoDB 콘솔 <https://aws.amazon.com/ko/pm/dynamodb/>_ 에 접속한다. 2.새 테이블 생성(Create Table) 을 선택한다. 3.테이블 키 구성:

Partition Key: sample_time (String)

Sort Key: device_id (String)

속성 추가: device_data (String)

4.생성 완료 후, AWS IoT Core에서 데이터를 저장할 수 있도록 IAM 역할 및 정책을 설정한다.

그림 57 DynamoDB 테이블 생성

DynamoDB테이블

그림 57 DynamoDB테이블

AWS IoT Core에서 수신한 MQTT 메시지를 DynamoDB에 저장하는 규칙을 생성한다.

1.AWS IoT 콘솔에서 메시지 라우팅 > 규칙(Rules) 로 이동한다.

2.규칙 생성(Create Rule) 을 선택한다.

3.규칙 세부 정보 입력:

Rule Name: IoTToDynamoDB SQL Statement:

SELECT sample_time, device_id, device_data FROM 'iot/sensor/data'

4.Set actions 단계에서 DynamoDBv2 를 선택하고, 다음을 입력한다:

Table Name: 생성한 DynamoDB 테이블 선택 Hash Key: sample_time → ${sample_time} Sort Key: device_id → ${device_id}

IAM Role: AWS IoT Core가 DynamoDB에 데이터를 삽입할 수 있도록 설정한 IAM 역할 적용

그림 58 DynamoDB에 데이터를 전송하는 AWS IoT 규칙 생성​

규칙생성

그림 58 규칙생성

그림 59 DynamoDB 테이블 테스트

AWS IoT Core에서 DynamoDB로 데이터가 정상적으로 저장되는지 테스트한다.

1.AWS IoT 콘솔에서 MQTT Test 클라이언트 를 실행한다. 2.다음과 같은 JSON 메시지를 iot/sensor/data 주제로 게시한다:

{
   "sample_time": "2025-02-27T12:00:00Z",
   "device_id": "sensor_001",
   "device_data": "temperature:22.5,humidity:60"
}

3.DynamoDB 콘솔에서 해당 테이블을 조회하고 데이터가 정상적으로 저장되었는지 확인한다.

실행결과

그림 59 실행결과

Lambda 함수

Lambda 콘솔

Lambda 함수 생성 여기서 예제를 수행하여 Lambda함수를 사용하는 법을 공부.

1.Lambda 콘솔의 함수 페이지를 연다.

2.함수 생성을 선택한다.

3.새로 작성을 선택한다.

4.기본 정보 창의 함수 이름에 myLambdaFunction을 입력한다.

5.런타임에서 Node.js 22.x 또는 Python 3.13을 선택한다.

6.아키텍처를 x86_64로 설정된 상태로 두고 함수 생성을 선택한다.

7.아래 코드를 넣는다.

export const handler = async (event, context) => {

const length = event.length;
const width = event.width;
let area = calculateArea(length, width);
console.log(`The area is ${area}`);

console.log('CloudWatch log group: ', context.logGroupName);

let data = {
    "area": area,
};
    return JSON.stringify(data);

function calculateArea(length, width) {
    return length * width;
}
};

그림 60 람다함수를 생성하여 예제코드를 넣은 모습

Lambda예제코드

그림 60 Lambda예제코드

8.Deploy 섹션에서 Deploy를 선택하여 함수의 코드를 업데이트한다.

9.콘솔 코드 편집기의 테스트 이벤트 섹션에서 테스트 이벤트 생성을 선택한다.

10.Event Name(이벤트 이름)에 myTestEvent를 입력한다.

11.이벤트 JSON 섹션에서 기본 JSON을 다음으로 바꾼다.

{
"length": 6,
"width": 7
}

그림 61 함수에 전송할 테스트 이벤트​

Lambda테스트이벤트

그림 61 Lambda테스트이벤트

12.콘솔 코드 편집기의 테스트 이벤트 섹션에서 테스트 이벤트 옆에 있는 실행 아이콘을 선택한다.

그림 62 실행결과 화면

실행결과

그림 62 실행결과

13.CloudWatch 콘솔에서 로그 그룹 페이지를 연다.

14.함수에 대한 로그 그룹(/aws/lambda/myLambdaFunction)을 선택한다. 이는 함수가 콘솔에 인쇄한 로그 그룹 이름이다.

15.아래로 스크롤하고 확인하고자 하는 함수 간접 호출의 로그 스트림을 선택한다.

그림 63 CloudWatch Logs에서 함수의 간접 호출 레코드 보기​

CloudWatch Logs

그림 63 CloudWatch Logs


01월 26일 - 02월 01일 (설 연휴)

재충전


02월 02일 - 02월 08일 (03주차)

RAK11310보드를 RAK4630보드로 교체했다.

RAK4630

1.Arduino IDE를 엽니다.

2.도구 메뉴 에서 보드 관리자를 엽니다. 최신 버전의 RAKwireless RUI nRF 보드를 선택하여 설치합니다.

RAK1901

보드와 직렬포트를 선택하고 위의 온,습도 예제코드를 업로드한다.

aws iot core에 새로운 게이트웨이 등록 RAK7248 게이트웨이 등록

LoRaWAN을 위해 AWS IoT Core에 게이트웨이를 등록하려면 다음 단계를 실행하세요.

1.AWS IoT 콘솔 로 이동합니다.

2.왼쪽 탐색 패널에서 LPWAN 장치를 선택하세요.

3.게이트웨이를 선택한 다음 게이트웨이 추가를 클릭합니다.

4.게이트웨이 추가 섹션 에서 게이트웨이의 EUI 및 주파수 대역(RF 지역) 필드를 입력합니다.

5.이름 : 선택 필드 에 설명적 이름을 입력합니다 . 이름으로 GatewayEUI를 사용하는 것이 좋습니다.

6.게이트웨이 추가를 클릭합니다.

7.게이트웨이 구성 페이지 에서 게이트웨이 인증서 라는 섹션을 찾으세요.

8.인증서 만들기를 선택하세요.

9.인증서가 생성되어 게이트웨이 메시지와 연결 되면 인증서 파일 다운로드를 선택하여 인증서( xxxxx.cert.pem )와 개인 키( xxxxxx.private.key )를 다운로드합니다.

10.자격 증명 프로비저닝 섹션에서 서버 신뢰 인증서 다운로드를 선택하여 CUPS(cups.trust) 및 LNS(lns.trust) 서버 신뢰 인증서를 다운로드합니다.

11.CUPS 및 LNS 엔드포인트를 복사하여 .txt게이트웨이를 구성하는 동안 사용할 파일에 저장합니다.

12.제출을 선택하여 게이트웨이를 추가합니다.

그림 64 인증서 파일들

인증서 파일들

그림 64 인증서 파일들

게이트웨이 디바이스 구성

1.LoRaWAN용 AWS IoT Core에 연결하려면 BasicsStation을 다운로드하고 컴파일해야 합니다. SSH를 통해 게이트웨이에 성공적으로 액세스했다고 가정하면 업데이트/업그레이드하는 것이 좋은 정책입니다. 그렇게 하면 모든 패키지가 최신 상태로 유지됩니다:

sudo apt update
sudo apt upgrade

2.이제 LoRaWAN용 AWS IoT Core에서 게이트웨이를 등록합니다. 게이트웨이를 등록하려면 장치의 EUI가 필요합니다. 게이트웨이의 EUI를 찾으려면 아래 명령을 실행하여 그래픽 사용자 인터페이스(GUI)를 엽니다. EUI는 GUI 상단에서 찾을 수 있습니다:

sudo gateway-config

또한 다음 명령을 실행하여 게이트웨이 버전을 확인할 수도 있습니다:

sudo gateway-version

출력 예시:

Raspberry Pi 4 Model B Rev 1.1, OS "10 (buster)", 5.4.79-v7l+.
RAKwireless gateway RAK7248 no LTE version 4.2.7R install from firmware
Gateway ID: DCA632FFFE366417

기본 스테이션(Basics Station) 구성

3.Basics Station 저장소를 복제하고 다운로드한 폴더로 이동합니다:

git clone https://github.com/lorabasics/basicstation.git
cd basicstation

4.모든 종속성을 설치합니다:

make platform=corecell variant=std

5.특정 파일을 복사하여 교체합니다:

sudo cp -f /opt/ttn-gateway/sx1302_hal/libloragw/src/loragw_stts751.c deps/lgw1302/platform-corecell/libloragw/src/loragw_stts751.c

6.빌드를 정리하고 다시 컴파일합니다:

make clean
make platform=corecell variant=std

7.설치가 완료되면 Basics Station 프로토콜을 구성해야 합니다. Semtech에서 제공한 예제가 있는 폴더를 입력하고, 특히 빌드가 SX1302용이므로 corecell 폴더를 입력합니다.

cd examples/corecell

8.reset_gw.sh를 수정하여 리셋 핀을 17로 변경하세요 .

#!/bin/sh

# This script is intended to be used on SX1302 CoreCell platform, it performs the following actions:
#        - export/unexport GPI023 and GPI018 used to reset the SX1302 chip and to enable the LDOs
#
# Usage examples:
#       ./reset_lgw.sh stop
#       ./reset_lgw.sh start
#
# GPIO mapping has to be adopted with HW
#

SX1302_RESET_PIN=17
SX1302_POWER_EN_PIN=18

WAIT_GPIO() {
    sleep 0.1
}

9.LoRaWAN을 위한 AWS IoT Core에 연결하기 위한 기본 스테이션의 모든 구성 파일을 저장할 새 폴더를 만듭니다.

mkdir lns-aws

10.기본 스테이션 구성 파일을 복사합니다:

cp lns-ttn/station.conf lns-aws/

11.이전에 다운로드한 인증서를 추가합니다:

ls -all lns-aws

그림 65 cups.trust와 cups.uri

cups.trust와 cups.uri

그림 65 cups.trust와 cups.uri

그림 66 cups.crt와 cups.key

cups.crt와 cups.key

그림 66 cups.crt와 cups.key

출력 예시: 그림 67

모든 설치가 끝난 후의 파일들

그림 67 모든 설치가 끝난 후의 파일들

12.기본 스테이션을 사용하기 전에 기존 패킷 포워더를 비활성화해야 합니다. 패킷 포워더의 서비스를 편집하려면 다음을 실행합니다:

sudo systemctl edit ttn-gateway.service
[Unit]
Description=The Things Network Gateway

[Service]
WorkingDirectory=/opt/ttn-gateway/packet_forwarder/lora_pkt_fwd/
ExecStart=/opt/ttn-gateway/packet_forwarder/lora_pkt_fwd/start.sh
SyslogIdentifier=ttn-gateway
Restart=no
RestartSec=5

[Install]
WantedBy=multi-user.target

13.파일에 다음 설정을 추가합니다:

[Unit]
Description=The Things Network Gateway

[Service]
WorkingDirectory=/opt/ttn-gateway/packet_forwarder/lora_pkt_fwd/
ExecStart=/opt/ttn-gateway/packet_forwarder/lora_pkt_fwd/start.sh
SyslogIdentifier=ttn-gateway
Restart=no
RestartSec=5

[Install]
WantedBy=multi-user.target

14.변경 사항을 적용하려면 다음 명령을 실행합니다:

sudo systemctl daemon-reload
sudo systemctl restart ttn-gateway.service

15.기존 패킷 포워더 프로세스를 종료하려면 다음 명령을 실행합니다:

pi@rak-gateway:~/basicstation/example/corecell/lns-aws $ ps aux | grep ttn-gateway

16.출력된 프로세스 ID(PID)를 찾아 종료합니다:

sudo kill <PID>

17.이제 기본 스테이션을 시작할 수 있습니다. 구성 폴더로 이동한 후 실행합니다:

cd ..
./start-station.sh -l ./lns-aws

모든 것이 올바르게 구성되었다면, 게이트웨이는 AWS IoT Core for LoRaWAN 콘솔에서 온라인 상태로 나타나야 합니다.

그림 68 aws iot core에 connected된 화면

aws iot core에 connected된 화면

그림 68 aws iot core에 connected된 화면

그림 69 예제 실행결과

예제 실행결과

그림 69 예제 실행결과

아두이노 IDE 예제코드 수정

1.RAK WisBlock RUI 예제 에서 예제 코드를 연다.

그림 70 LoRaWAN예제 여는법

OTAA예제

그림 70 OTAA예제

2.예제 코드에서 nodeDeviceEUI와 nodeAppEUI, nodeAppKey를 수정한다.

3.주파수를 사용하는 주파수에 맞게 수정한다. (KR920)

그림 71 LoRaWAN예제 키와 주파수 수정​

OTAA예제수정

그림 71 OTAA예제수정

RAK4630 디바이스 등록

프로필 추가

1.AWS IoT 콘솔로 이동한다. 탐색 창에서 LPWAN devices(LPWAN 디바이스)를 선택한 다음 Profiles(프로필)를 클릭한다.

2.Device Profiles(디바이스 프로필)추가를 클릭하고 매개변수를 설정한다.

3.Add service profile(서비스 프로필 추가)을 클릭하고 필요에 따라 매개 변수를 설정한다.

그림 72 디바이스 프로필 추가한 모습​

디바이스프로필

그림 72 디바이스프로필

그림 73 서비스 프로필 추가한 모습​

서비스프로필

그림 73 서비스프로필

대상에 IAM 역할 추가

1.IAM 콘솔로 이동한다. 탐색 창에서 정책(Policies)을 선택한다.

2.[Create Policy]를 선택한 다음 [JSON] 탭을 선택하여 정책 편집기를 연다. 기존 템플릿을 다음 신뢰 정책 문서로 바꾼다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:DescribeEndpoint",
                "iot:Publish"
            ],
            "Resource": "*"
        }
    ]
}

3.다음을 클릭하고 Review and create(검토 및 만들기) 페이지에서 선택한다. 이름에 원하는 이름을 입력한다.

4.[Description]에 원하는 설명을 입력한다.

5.정책 생성을 선택한다. 정책이 생성되었음을 나타내는 확인 메시지가 표시된다.

(역할 생성)

6.탐색 창에서 [Roles]를 선택하여 [Roles] 페이지를 연다.

7.[Create Role]을 선택한다.

8.[Create Role] 페이지에서 [AWS account] > [Another AWS account]를 선택한다.

9.계정 ID를 입력하고 다음을 선택한다.

10.검색 창에 정책 이름을 입력하여 방금 생성한 IAM 정책을 검색한다.

11.검색 결과에서 정책에 해당하는 확인란을 선택한다. 다음을 클릭한다.

12.[Role name]에 원하는 적절한 이름을 입력한다.

13.[Description]에 원하는 설명을 입력한다.

14.[Create role]을 선택한다. 역할이 생성되었음을 나타내는 확인 메시지가 표시된다.

(신뢰정책 업데이트)

15.탐색 창에서 [Roles]를 선택하여 [Roles] 페이지를 연다.

16.검색 창에서 이전에 생성한 역할의 이름을 입력하고 검색 결과에서 역할 이름을 클릭한다. 그러면 요약 페이지가 열린다.

17.신뢰 관계(Trust relationships) 탭을 선택하여 신뢰 관계(Trust relationships) 페이지로 이동한다.

18.신뢰 정책 편집을 클릭한다. 신뢰 정책 문서의 보안 주체 AWS 역할은 기본적으로 root로 설정되며 변경해야 한다. 기존 정책을 다음으로 바꾼다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                    "Service": "iotwireless.amazonaws.com"
                },
            "Action": "sts:AssumeRole",
            "Condition": {}
        }
    ]
}

19.정책 업데이트를 선택한다.

대상 설정

1.탐색 창에서 LPWAN 디바이스(LPWAN devices)를 선택한 다음 대상(Destinations)을 선택한다.

2.[Add Destination]을 선택한다.

3.대상 이름에 ProcessLoRa를 입력한 다음 대상 설명 - 선택 사항 아래에 적절한 설명을 추가한다.

4.규칙 이름에 LoRaWANRouting을 입력한다. 규칙 구성 – 지금은 선택 사항 섹션을 무시한다.

5.[Permissions] 섹션에서 [Select an existing service role]을 선택하고 드롭다운에서 이전에 생성한 IAM 역할을 선택한다.

6.[Add Destination]을 선택한다. 대상이 성공적으로 추가되었음을 나타내는 “Destination added(대상이 추가됨)” 메시지가 표시된다.

대상 규칙

1.AWS IoT 콘솔로 이동한다.

2.탐색 창에서 메시지 라우팅(Message routing)을 선택한 다음 규칙(Rules)을 선택한다.

3.규칙(Rules) 페이지에서 규칙 생성(Create rule)을 선택한다.

5.Create a rule(규칙 생성) 페이지에서 다음과 같이 입력한다.

이름: LoRaWANRouting

설명: 선택한 설명입니다.다음을 클릭합니다.

6.기본 규칙 쿼리 문인 ‘SELECT * FROM ‘iot/topic’을 변경하지 않고 그대로 둔다. 이 쿼리는 현재 트래픽이 대상에 따라 규칙 엔진으로 전달되므로 현재 적용되지 않는다. 다음을 클릭한다.

7.[Attach rule actions] 페이지에서 [Republish to AWS IoT topic]을 선택한다.

8.[Topic]에 project/sensor/decoded를 입력합니다. AWS IoT 규칙 엔진은 이 주제로 메시지를 전달한다.

9.IAM 역할에서 새 역할 만들기를 선택한다.

10.이름에 원하는 이름을 입력한다. 만들기를 클릭한다.

11.그런 다음 [Create]를 선택한다.

12.”성공” 메시지가 패널 맨 위에 표시되고 대상에는 규칙이 바인딩되어 있다.

장치 등록

1.AWS IoT 콘솔로 이동한다.

2.왼쪽의 탐색 패널에서 LPWAN 장치를 선택한다.

3.Devices(장치)를 선택한 다음 Add wireless device(무선 장치 추가)를 선택한다.

4.Add device(디바이스 추가) 페이지의 Wireless device specification(무선 디바이스 사양) 아래의 드롭다운에서 LoRaWAN 사양 버전을 선택한다.

5.LoRaWAN 사양 및 무선 디바이스 구성에서 DevEUI를 입력한다.

6.위에서 선택한 OTAA/ABP에 따라 나머지 필드를 입력한다.

7.Wireless device name – optional 필드에 장치 이름을 입력한다.

8.프로필 섹션의 무선 장치 프로필 아래에 있는 드롭다운 옵션에서 만든 장치 프로필 또는 장치 및 지역에 해당하는 장치 프로필을 찾는다.

9.Choose destination(대상 선택) 아래의 드롭다운에서 이전에 생성한 대상(ProcessLoRa)을 선택한다.

10.다음을 클릭한다.

11.장치 추가를 선택한다. “무선 장치가 추가됨”이라는 메시지가 표시되어 장치가 성공적으로 설정되었음을 나타낸다.

12.최종 디바이스를 다시 시작하면 AWS IoT LoRaWAN 서버에 조인된다.

13.MQTT로 HELLOW!를 인코딩해서 보내는데 성공​

LoRaWAN으로 온, 습도값 보내기

1.아두이노 IDE를 연다.

2.RAK WisBlock RUI 예제 에서 OTAA_ABP예제 코드를 연다.

3.아래 예제로 코드를 바꾼후 업로드 한다.

#include <Arduino.h>
#include <LoRaWan-RAK4630.h> //http://librarymanager/All#SX126x
#include <SPI.h>

// RAK4630 supply two LED
#ifndef LED_BUILTIN
#define LED_BUILTIN 35
#endif

#ifndef LED_BUILTIN2
#define LED_BUILTIN2 36
#endif

// 마지막으로 센서를 읽은 시간
unsigned long lastSensorReadTime = 0;
const unsigned long sensorReadInterval = 10000; // 10초마다 읽기
float lastTemperature = 0.0;
float lastHumidity = 0.0;




#include "SparkFun_SHTC3.h"       //Click here to get the library: http://librarymanager/All#SparkFun_SHTC3
SHTC3 g_shtc3;
                        // Declare an instance of the SHTC3 class


bool doOTAA = true;   // OTAA is used by default.
#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE /**< Maximum size of scheduler events. */
#define SCHED_QUEUE_SIZE 60                                /**< Maximum number of events in the scheduler queue. */
#define LORAWAN_DATERATE DR_0                             /*LoRaMac datarates definition, from DR_0 to DR_5*/
#define LORAWAN_TX_POWER TX_POWER_5                     /*LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15*/
#define JOINREQ_NBTRIALS 3                                /**< Number of trials for the join request. */
DeviceClass_t g_CurrentClass = CLASS_A;               /* class definition*/
LoRaMacRegion_t g_CurrentRegion = LORAMAC_REGION_KR920;    /* Region:EU868*/
lmh_confirm g_CurrentConfirm = LMH_UNCONFIRMED_MSG;              /* confirm/unconfirm packet definition*/
uint8_t gAppPort = LORAWAN_APP_PORT;                             /* data port*/

/**@brief Structure containing LoRaWan parameters, needed for lmh_init()
*/
static lmh_param_t g_lora_param_init = {LORAWAN_ADR_ON, LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};

// Foward declaration
static void lorawan_has_joined_handler(void);
static void lorawan_join_failed_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
static void send_lora_frame(void);

/**@brief Structure containing LoRaWan callback functions, needed for lmh_init()
*/
static lmh_callback_t g_lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
                                        lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler
                                    };
//OTAA keys !!!! KEYS ARE MSB !!!!
uint8_t nodeDeviceEUI[8] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x32, 0x39};
uint8_t nodeAppEUI[8] = {0xB8, 0x27, 0xEB, 0xFF, 0xFE, 0x39, 0x12, 0x00};
uint8_t nodeAppKey[16] = {0x88, 0x38, 0x57, 0x86, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88};

// ABP keys
uint32_t nodeDevAddr = 0x260116F8;
uint8_t nodeNwsKey[16] = {0x7E, 0xAC, 0xE2, 0x55, 0xB8, 0xA5, 0xE2, 0x69, 0x91, 0x51, 0x96, 0x06, 0x47, 0x56, 0x9D, 0x23};
uint8_t nodeAppsKey[16] = {0xFB, 0xAC, 0xB6, 0x47, 0xF3, 0x58, 0x45, 0xC7, 0x50, 0x7D, 0xBF, 0x16, 0x8B, 0xA8, 0xC1, 0x7C};

// Private defination
#define LORAWAN_APP_DATA_BUFF_SIZE 64                     /**< buffer size of the data to be transmitted. */
#define LORAWAN_APP_INTERVAL 20000                        /**< Defines for user timer, the application data transmission interval. 20s, value in [ms]. */
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];            //< Lora user application data buffer.
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0}; //< Lora user application data structure.

TimerEvent_t appTimer;
static uint32_t timers_init(void);
static uint32_t count = 0;
static uint32_t count_fail = 0;

void setup()
{

pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);

// Initialize Serial for debug output
time_t timeout = millis();
Serial.begin(115200);
Wire.begin();  // I2C 초기화는 setup()에서  번만 실행
Wire.setClock(400000);  // I2C 속도 설정
Wire.setTimeout(10);

g_shtc3.begin();
if (g_shtc3.lastStatus != SHTC3_Status_Nominal) {
        Serial.println("⚠️ 센서 초기화 실패!");
    } else {
        Serial.println("✅ 센서 정상 연결됨!");
    }
while (!Serial)
{
    if ((millis() - timeout) < 5000)
    {
    delay(100);
    }
    else
    {
    break;
    }
}

// Initialize LoRa chip.
lora_rak4630_init();

Serial.println("=====================================");
Serial.println("Welcome to RAK4630 LoRaWan!!!");
if (doOTAA)
{
    Serial.println("Type: OTAA");
}
else
{
    Serial.println("Type: ABP");
}

switch (g_CurrentRegion)
{
    case LORAMAC_REGION_AS923:
    Serial.println("Region: AS923");
    break;
    case LORAMAC_REGION_AU915:
    Serial.println("Region: AU915");
    break;
    case LORAMAC_REGION_CN470:
    Serial.println("Region: CN470");
    break;
case LORAMAC_REGION_CN779:
    Serial.println("Region: CN779");
    break;
    case LORAMAC_REGION_EU433:
    Serial.println("Region: EU433");
    break;
    case LORAMAC_REGION_IN865:
    Serial.println("Region: IN865");
    break;
    case LORAMAC_REGION_EU868:
    Serial.println("Region: EU868");
    break;
    case LORAMAC_REGION_KR920:
    Serial.println("Region: KR920");
    break;
    case LORAMAC_REGION_US915:
    Serial.println("Region: US915");
    break;
case LORAMAC_REGION_RU864:
    Serial.println("Region: RU864");
    break;
case LORAMAC_REGION_AS923_2:
    Serial.println("Region: AS923-2");
    break;
case LORAMAC_REGION_AS923_3:
    Serial.println("Region: AS923-3");
    break;
case LORAMAC_REGION_AS923_4:
    Serial.println("Region: AS923-4");
    break;
}
Serial.println("=====================================");

//creat a user timer to send data to server period
Serial.println("타이머 초기화 시작!");
uint32_t err_code;
err_code = timers_init();
if (err_code != 0)
{
    Serial.printf("timers_init failed - %d\n", err_code);
    return;
}
Serial.println("타이머 초기화 완료!");

// Setup the EUIs and Keys
if (doOTAA)
{
    lmh_setDevEui(nodeDeviceEUI);
    lmh_setAppEui(nodeAppEUI);
    lmh_setAppKey(nodeAppKey);
}
else
{
    lmh_setNwkSKey(nodeNwsKey);
    lmh_setAppSKey(nodeAppsKey);
    lmh_setDevAddr(nodeDevAddr);
}

// Initialize LoRaWan
Serial.println("LoRa 모듈 초기화 시작!");
err_code = lmh_init(&g_lora_callbacks, g_lora_param_init, doOTAA, g_CurrentClass, g_CurrentRegion);
if (err_code != 0)
{
    Serial.printf("lmh_init failed - %d\n", err_code);
    return;
}
Serial.println("LoRa 모듈 초기화 완료!");

// Start Join procedure
Serial.println("LoRaWAN Join 요청 시작!");
lmh_join();
}



/**@brief LoRa function for handling HasJoined event.
*/
void lorawan_has_joined_handler(void)
{
if(doOTAA == true)
{
Serial.println("OTAA Mode, Network Joined!");
}
else
{
    Serial.println("ABP Mode");
}

lmh_error_status ret = lmh_class_request(g_CurrentClass);
if (ret == LMH_SUCCESS)
{
    Serial.println("타이머 시작!");
    delay(1000);
    TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
    TimerStart(&appTimer);
    Serial.printf("타이머 실행됨! 현재 상태: %d\n", appTimer.IsRunning);

    Serial.println("타이머 강제 실행 테스트!");
    tx_lora_periodic_handler();
}


}
/**@brief LoRa function for handling OTAA join failed
*/
static void lorawan_join_failed_handler(void)
{
Serial.println("OTAA join failed!");
Serial.println("Check your EUI's and Keys's!");
Serial.println("Check if a Gateway is in range!");
}
/**@brief Function for handling LoRaWan received data from Gateway
*
* @param[in] app_data  Pointer to rx data
*/
void lorawan_rx_handler(lmh_app_data_t *app_data)
{
Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d, data:%s\n",
        app_data->port, app_data->buffsize, app_data->rssi, app_data->snr, app_data->buffer);
}

void lorawan_confirm_class_handler(DeviceClass_t Class)
{
Serial.printf("switch to class %c done\n", "ABC"[Class]);
// Informs the server that switch has occurred ASAP
m_lora_app_data.buffsize = 0;
m_lora_app_data.port = gAppPort;
lmh_send(&m_lora_app_data, g_CurrentConfirm);
}


void send_lora_frame(void)
{
if (lmh_join_status_get() != LMH_SET)
{
    //Not joined, try again later
    return;
}

uint32_t i = 0;
memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
m_lora_app_data.port = gAppPort;
Serial.printf("📦 전송 데이터 -> 온도: %.2f°C, 습도: %.2f%%\n", lastTemperature, lastHumidity);

if ((int)(lastHumidity * 100) == 0)
{

Serial.printf("습도가 0이면 전송x");
}
if ((int)(lastHumidity * 100) != 0)
{


// LoRaWAN 데이터 패킷 생성 (온도  습도 2바이트씩 전송)
    m_lora_app_data.buffer[i++] = (uint8_t)lastTemperature;                         // 온도 정수 부분
    m_lora_app_data.buffer[i++] = (uint8_t)((lastTemperature - (int)lastTemperature) * 100);  // 온도 소수 부분
    m_lora_app_data.buffer[i++] = (uint8_t)lastHumidity;                            // 습도 정수 부분
    m_lora_app_data.buffer[i++] = (uint8_t)((lastHumidity - (int)lastHumidity) * 100);  // 습도 소수 부분
    m_lora_app_data.buffsize = i;

}



lmh_error_status error = lmh_send(&m_lora_app_data, g_CurrentConfirm);
if (error == LMH_SUCCESS)
{
    count++;
    Serial.printf("lmh_send ok count %d\n", count);
}
else
{
    count_fail++;
    Serial.printf("lmh_send fail count %d\n", count_fail);
}
}

void read_shtc3_data()
{
    if (millis() - lastSensorReadTime >= sensorReadInterval) {
        lastSensorReadTime = millis(); // 마지막 실행 시간 업데이트

        g_shtc3.update();


        yield();


        if (g_shtc3.lastStatus == SHTC3_Status_Nominal) {
            lastTemperature = g_shtc3.toDegC();
            lastHumidity = g_shtc3.toPercent();
            Serial.printf("📊 센서 데이터: 온도 = %.2f°C, 습도 = %.2f%%\n", lastTemperature, lastHumidity);
        } else {
            Serial.println("⚠️ 센서 데이터 읽기 실패!");
        }
    }
}


/**@brief Function for the Timer initialization.
*
* @details Initializes the timer module. This creates and starts application timers.
*/


void tx_lora_periodic_handler()
{
    Serial.println("📡 LoRaWAN 데이터 전송 시작");


    // LoRa 데이터 전송
    send_lora_frame();

    // 타이머 재설정
    TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
    TimerStart(&appTimer);
}


void resetI2CBus() {
    Serial.println("🔄 I2C 버스 리셋 실행");
    Wire.end();  // 기존 I2C 종료
    delay(100);  // 잠시 대기
    Wire.begin();  // 다시 시작
    Wire.setClock(100000);
    Serial.println("✅ I2C 버스 재시작 완료");
}
// 타이머 초기화
uint32_t timers_init()
{
    TimerInit(&appTimer, tx_lora_periodic_handler);
    return 0;
}

void loop()
{
    read_shtc3_data(); // 센서 데이터 비동기 읽기
    yield();

    delay(3000); // CPU 점유율 최소화
}

그림 74 MQTT로 인코딩된 온, 습도값을 받는데 성공

MQTT결과

그림 74 MQTT결과

람다로 보내서 DynamoDB에 저장

1.DvnamoDB 콘솔의 테이블 페이지를 연다.

2.테이블생성을 선택한다.

테이블 이름: LoRaWAN_SensorData 파티션 키 (Primary Key): DeviceId (String) 정렬 키 (Sort Key): Timestamp (Number)

3.기타 설정을 기본값으로 두고 생성 한다.

4.Lambda 콘솔의 함수 페이지를 연다.

5.함수 생성을 선택한다.

6.새로 작성을 선택한다.

7.기본 정보 창의 함수 이름에 LoRaWAN_SaveToDynamoDB을 입력한다.

8.런타임에서 Python 3.13을 선택한다.

9.아키텍처를 x86_64로 설정된 상태로 두고 함수 생성을 선택한다.

10.아래 코드를 넣는다.

import json
import boto3
import base64
import datetime

# ✅ DynamoDB 연결
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('LoRaWAN_SensorData')  # 테이블 이름 확인!

def decode_payload(payload):
    """Base64로 인코딩된 센서 데이터를 디코딩하여 온도와 습도를 추출"""
    try:
        decoded_bytes = base64.b64decode(payload)  # Base64 디코딩
        if len(decoded_bytes) < 2:
            raise ValueError("디코딩된 데이터 길이가 너무 짧음")

        temperature = decoded_bytes[0]  # 첫 번째 바이트: 온도 (정수 값)
        humidity = decoded_bytes[2]  # 두 번째 바이트: 습도 (정수 값)

        return temperature, humidity
    except Exception as e:
        print(f"❌ 페이로드 디코딩 실패: {e}")
        return None, None

def lambda_handler(event, context):
    """AWS IoT Core에서 수신한 MQTT 메시지를 처리하고 DynamoDB에 저장"""
    try:
        print("📡 수신된 이벤트: ", json.dumps(event))

        # ✅ JSON 데이터에서 PayloadData 및 메타데이터 추출
        payload_data = event.get("PayloadData", "")
        device_id = event.get("WirelessMetadata", {}).get("LoRaWAN", {}).get("DevEui", None)
        timestamp_str = event.get("WirelessMetadata", {}).get("LoRaWAN", {}).get("Timestamp", None)

        print(f"🛠️ 디바이스 ID 확인: {device_id}")

        # ✅ 필수 데이터 확인
        if not device_id:
            raise ValueError("❌ 디바이스 ID 없음 (DevEui 값 확인 필요)")

        if not timestamp_str:
            raise ValueError("❌ Timestamp 값 없음!")

        # ✅ UTC 타임스탬프 변환
        timestamp = int(datetime.datetime.strptime(timestamp_str, "%Y-%m-%dT%H:%M:%SZ").timestamp())

        # ✅ Base64 디코딩하여 온도 및 습도 추출
        temperature, humidity = decode_payload(payload_data)

        if temperature is None or humidity is None:
            raise ValueError("❌ 온도 또는 습도 데이터를 추출할 수 없음")

        # ✅ DynamoDB에 저장 (Key 이름 `DeviceId`, `Timestamp` 사용)
        response = table.put_item(
            Item={
                'DeviceId': device_id,  # 대소문자 구분
                'Timestamp': timestamp,  # 정렬 키 (Number)
                'Temperature': temperature,
                'Humidity': humidity
            }
        )

        print(f"✅ DynamoDB 저장 완료: {response}")

        return {
            'statusCode': 200,
            'body': json.dumps('Data successfully stored in DynamoDB!')
        }

    except Exception as e:
        print(f"❌ 오류 발생: {e}")
        return {
            'statusCode': 500,
            'body': json.dumps(f'Error: {str(e)}')
        }

11.Deploy 섹션에서 Deploy를 선택하여 함수의 코드를 업데이트한다.

12.AWS IoT 콘솔로 이동한다.

13.탐색 창에서 메시지 라우팅(Message routing)을 선택한 다음 규칙(Rules)을 선택한다.

14.위에서 만든 LoRaWANRouting 규칙을 누른다.

15.편집을 들어가서 [규칙작업 추가]를 누른다.

16.위에서 만든 LoRaWAN_SaveToDynamoDB 람다함수를 추가한다.

17.업데이트를 클릭한다.

그림 75 와 같이 온, 습도값이 DynamoDB에 쌓인다.

데이터베이스

그림 75 데이터베이스


결산

RAK11310에서 RAK4630으로 보드를 바꿈. AWS에 대해 공부함. 디바이스와 게이트웨이를 LoRaWAN으로 연결하여 서버에서 MQTT로 데이터 받는데 성공 온, 습도값을 LoRaWAN으로 보내서 DynamoDB에 쌓는데 성공


기타

디바이스를 등록할때 권한을 꼬이지 않게 잘 지정해야하고 Destination에서 RULE추가를 해줘야 한다.

LoRaWAN으로 온, 습도값을 보내는 코드를 짤때 핸들러가 멈춰서 강제실행하는 Yeild를 추가하여 해결했다.



연구 노트 2025_0209_0301_SPRT_07_09 RX004 01조

개요

저해상도 적외선 카메라로 촬영한 이미지를 mqtt 메세지로 보내 amazon s3에 저장한다.


02월 09일 - 02월 15일 (01주차)

목표

  1. 단말 장치에 태양광 패널을 사용한 에너지 수확 기능 추가

  2. rak19007 wisboard의 적외선 카메라(RAK12052) 추가 및 device data Aws IOT core 에서 S3에 저장


참고링크: RAK7248 가이드, RAK7268 가이드, RAK1901 가이드, DynamoDB 문서 , Lambda 문서 , RAK12052문서, RAK19007 wisboard, 태양광 패널 설치 가이드, IR Array Module 가이드


과정

  • 단말 장치에 태양광 패널을 사용한 에너지 수확 기능 추가
    • solar bettery 설치하기
      1. RAK19007 wisboard 를 참고하여 Ctrl+F로 ‘Solar Panel Connector’를 검색하면 태양광 패널이 자동으로 에너지를 수확하여 리튬 이온 배터리로 저장되는 것을 확인할 수 있다.

      2. Ctrl+F ‘Type-C USB port’ 를 검색하면 기존에 쓰던 device를 충전할 때 쓰는 C-type 단자를 충전기와 연결하면 자동으로 리튬 이온 배터리가 충전된다는 것을 확인할 수 있다.

      3. 태양광 패널 설치 가이드 를 참고하여 RAK19007 보드를 연결해준다.

      4. RAK19007 wisboard 의 Battery Connector, Solar Panel Connector 파트를 참고해 태양광 패널과 리튬 이온 배터리를 device에 달아준다.

      5. 자동으로 태양광 패널을 사용한 에너지 수확 기능 추가 되었는 지 확인하기 위하여 C-type 연결선을 제거하고 리튬 이온 배터리만을 이용하여 device를 구동해본다. 이전에 만들어둔 기본적인 온습도 예제를 사용하여 DynamoDB에 data가 저장되는지 확인하면 된다.

  • rak19007 wisboard의 적외선 카메라(RAK12052) 추가
    • IR camera module 설치
      1. IR Array Module 가이드 의 quick start guide에 들어가면 하드웨어를 wisboard에 설치하는 방법이 나와있다.

      2. 가이드를 따라 밑으로 이동하면 Aduino 예제가 나오는데 진행해볼 수 있다. Fileexamplesrak12052-MLX90640RAK12052_32X24_IR_Read_MLX90640 - RAK12052_32X24_IR_display_MLX90640 예제는 출력되는 화면이 따로 없으므로 사용하지 않았다. 화면 mobulde이 따로 없어도 아두이노의 serial monitor로 출력값이 어떻게 되는 지 확인 가능하다.

  • Device data Aws IOT core 에서 S3에 저장 서버 구축

    • 과정 정리

      1. device → MQTT(project/sensor/decoded)으로 전체 코드(온도 포함) 전송

      2. LoRaWANRouting/action3 에 새로운 Lambdafunction을 만들어 추가 → 온도를 사진으로 변환 → S3 저장

    • AWS IoT Core

      1. action3 추가

        AWS IoT/Message routing/Rules/LoRaWANRouting에서 deviced와 이미 연결되어 destination의 역할을 결정해주는 rule인 LoRaWANRouting의 action을 추가해 lambda 를 넣어준다.

      2. lambda fuction 추가

        lambda fuction: mqtt-to-s3-labdafuction 만들던거 그대로 role 추가해준다. (참고로 python을 쓰기 위해 blueprint 선택해주고 hellow world fuction python으로 지정한다)

        특정 Topic으로 받은 데이터를 사진으로 변환하여 이후 만들 S3/bucket으로 전송하는 코드를 만들어준다.

      3. role, policy 추가

        policy: mqtt-to-s3 로 람다 함수가 s3로 데이터를 넣을 수 있는 JSON을 만들어준다.

        role: mqtt-to-s3-role 위의 정책을 가진 role을 마저 만들어준다.

    • 3S

      1. Bucket 설정

        bucket/permissions/policy에서 람다에서 보내는 데이터를 저장해주는 policy를 추가해준다.

결과

  1. 단말 장치에 태양광 패널을 사용한 에너지 수확 기능 추가 및 구동

    → 성공

  2. rak19007 wisboard의 적외선 카메라(RAK12052) 추가 및 device data Aws IOT core 에서 S3에 저장

    → 환경은 구축했으나 아두이노에서 mqtt메세지를 보내는 부분이 완성되지 않아서 구동은 확인 불가


02월 16일 - 02월 22일 (02주차)

목표

  1. window pc를 이용해서 이미지를 mqtt로 보내 amazon s3에 저장하기


참고링크: Amazon S3, IoT Core, lambda


과정

• aws 설정
  • aws 버킷 생성
    1. Amazon S3 로 이동

    2. Creat bucket 선택

    3. 버킷 이름 지정 (본인이 원하는 이름으로)

    4. 나머지 설정은 기본값을 유지하고 버킷 생성

    생성된 S3 버킷

    그림 76 생성된 S3 버킷

  • aws IoT Core 설정(IoT thing 생성)
    1. IoT Core 로 이동

    2. Manage - All device - Things 이동

    3. Creat things 선택

    4. 본인이 원하는 이름 입력 후 next 선택

    5. Auto-generate new certificate 선택 후 next 선택

    6. creat policy 선택

    7. 본인이 원하는 이름 입력

    8. 아래에 policy document 밑에 json 선택

    9. 코드 편집기에 다음 코드 입력(region은 aws 리전, account-id는 본인 aws id)

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "iot:Publish",
            "iot:Subscribe",
            "iot:Receive",
            "iot:Connect"
          ],
          "Resource": [
            "arn:aws:iot:<region>:<account-id>:topic/iot/images",
            "arn:aws:iot:<region>:<account-id>:topicfilter/iot/images",
            "arn:aws:iot:<region>:<account-id>:client/*"
          ]
        }
      ]
    }
    
    1. create 선택

    2. 방금 생성한 policy 선택 후 create thing 선택

    생성된 thing

    그림 77 생성된 thing

  • 람다함수 생성하기
    1. lambda 로 이동

    2. Creat function 선택

    3. 본인이 원하는 이름 입력

    4. Runtime은 python 최신버전 선택

    5. creat function 선택

    6. 방금 생성한 람다함수 선택

    7. 아래에 Code 탭에 열려있는 lambda_function.py에 다음 코드를 입력

    import json
    import base64
    import boto3
    import time
    
    # AWS S3 클라이언트 생성
    s3 = boto3.client('s3')
    
    # S3 버킷 이름 (본인의 버킷 이름으로 변경)
    BUCKET_NAME = "m-bucket-test"
    
    def lambda_handler(event, context):
        try:
            # 로그 출력 (디버깅용)
            print("Received event:", json.dumps(event, indent=2))
    
            # MQTT 메시지에서 Base64 인코딩된 이미지 데이터 추출
            image_data_base64 = event.get('image_data', None)
            if not image_data_base64:
                raise ValueError("image_data 필드가 존재하지 않습니다.")
    
            # Base64 인코딩된 이미지 데이터를 디코딩
            image_data = base64.b64decode(image_data_base64)
    
            # 저장할 파일명 생성 (timestamp 기반)
            file_name = f"image_{int(time.time())}.jpg"
    
            # S3에 이미지 업로드
            s3.put_object(
                Bucket=BUCKET_NAME,
                Key=file_name,
                Body=image_data,
                ContentType="image/jpeg"
            )
    
            print(f"Image successfully saved to S3: {file_name}")
    
            return {
                'statusCode': 200,
                'body': json.dumps(f'Image uploaded: {file_name}')
            }
        except Exception as e:
            print(f"Error processing MQTT message: {str(e)}")
            return {
                'statusCode': 500,
                'body': json.dumps(f'Error: {str(e)}')
            }
    
    1. 왼쪽에 Deploy 선택

    생성된 람다

    그림 78 생성된 람다

    1. configuration 탭 선택 후 왼쪽의 목록에서 permissions 선택

    2. Role name 아래에 자동으로 생성된 role 하이퍼링크 클릭

    3. permissions - Add permissions - Attach policies 선택

    4. search 탭에 ‘AmazonS3FullAccess’ 검색 후 Add permissions 선택

    5. 같은 방법으로 ‘AWSIoTFullAccess’ permmissions 추가

    permission이 추가된 모습

    그림 79 permission이 추가된 모습

  • 람다 트리거 설정
    1. IoT Core 로 이동

    2. Manage - Message routing - Rules 이동

    3. Creat rule 선택

    4. 본인이 원하는 이름 입력 후 next 선택

    5. SQL version은 2016-03-23, SQL Statement에 다음 입력

    SELECT * FROM 'iot/images'
    
    1. Rule actions에서 Action 1에 Lambda 선택

    2. Lambda function에서 이전에 만든 Lambda function 선택 후 next 선택

    3. Creat 선택

    Rule이 추가된 모습

    그림 80 Rule이 추가된 모습

• 테스트
  • 이미지를 base64로 인코딩
    1. S3에 저장하려는 이미지를 jpg로 바탕화면에 저장

    2. 다음 파이썬 코드를 실행

    import base64
    import json
    
    image_path = r"C:\Users\사용자명\Desktop\test.jpg"  # 변환할 이미지 파일 (파일명을 본인의 이미지로 변경)
    
    with open(image_path, "rb") as image_file:
        encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
    
    mqtt_payload = json.dumps({"image_data": encoded_string})  # JSON 변환
    print(mqtt_payload)  # 전체 Base64 문자열 출력
    
  • 저장 테스트
    1. IoT Core 로 이동

    2. Test - MQTT test client로 이동

    3. Publish to a topic 선택

    4. Topic name에 iot/images입력

    5. Message payload에 python 코드를 실행한 결과를 그대로 붙혀넣기

    6. publish 선택

    7. Amazon S3 로 이동

    8. 본인이 생성한 버킷에 이미지가 저장됨

    이미지가 저장된 모습

    그림 81 이미지가 저장된 모습

    저장된 이미지를 확인할 수 있다

    그림 82 저장된 이미지를 확인할 수 있다

결과

  1. window pc를 이용해서 이미지를 mqtt로 보내 amazon s3에 저장하기

    –> 성공


02월 23일 - 03월 01일 (03주차)

더이상 진행할 수 있는 작업업이 없어서 문서화 작업 수행함.


결산

  1. 단말 장치에 태양광 패널을 사용한 에너지 수확 기능 추가
    –> 배터리를 장착하여 배터리를 사용해 디바이스가 동작하는 것을 확인함.
    –> 태양광 패널을 장착은 했지만 동작유무는 확인하지 못 함.
  2. 단말 장치에 저해상도 적외선 카메라 기능 추가

    –> 디바이스에 ir 카메라 모듈을 설치하고 파이썬을 이용해 동작을 확인함.

  3. 서버에 영상 데이터 저장 기능 추가
    –> 디바이스에서 mqtt 메세지로 사진파일 보내기는 실패함.
    –> window PC환경에서 mqtt 메세지로 사진을 보내 S3에 저장은 성공함.

기타



연구 노트 2025_0209_0301_SPRT_07_09 RX004 02조

개요

.


02월 09일 - 02월 15일 (01주차)

카메라와 RAK4630을 연결하고 태양광과 배터리를 연결하여 작동하는지 테스트 해봄.

그림 83 은 RAK4630을 하드웨어를 연결한 모습이다.

RAK4630

그림 83 RAK4630

카메라로 찍은 데이터 aws iot core에 보내는 코드 작성 (패킷문제 발생)


02월 16일 - 02월 22일 (02주차)

rx004 라이브러리 사용법 학습


02월 23일 - 03월 01일 (03주차)

개요

본 문서는 AWS IoT Core를 활용하여 MQTT 프로토콜을 통해 이미지를 전송하고, AWS Lambda를 이용하여 S3 버킷에 저장하는 과정에 대한 내용을 포함한다.


AWS 설정


S3 버킷 생성

AWS S3 콘솔로 이동한다.

“버킷 만들기”를 클릭한다.

버킷 이름: tests3coobarha

퍼블릭 액세스 차단 설정: “모든 퍼블릭 액세스 차단” 유지

“생성” 버튼을 클릭한다.


AWS IoT Core에서 Thing 생성

AWS IoT Core 콘솔로 이동한다.

“Manage” → “Things”를 선택한다.

“Create Things”를 클릭하여 “Create single thing”을 선택한다.

Thing 이름: iot-image-uploader 입력

Device Shadow: “Enable” 선택

“Next” 클릭 후 “Auto-generate new certificate” 선택

“Next” → “Create Thing” 클릭

그림 84 생성된 사물

생성된 사물

그림 84 생성된 사물


AWS IoT Core 인증서 다운로드

AWS IoT Core에서 아래 3개의 파일을 다운로드한다.

Device Certificate (.crt 파일)

Private Key (.key 파일)

Amazon Root CA (AmazonRootCA1.pem)

다운로드한 파일을 안전한 폴더에 저장한다.

예: C:\Users\dlwng\Downloads\


AWS IoT 정책 생성 (MQTT + S3 접근 허용)

AWS IoT Core 콘솔에서 “Secure” → “Policies” 클릭

“Create policy” 클릭

정책 이름: IoT_MQTT_S3_Policy

정책 문서(JSON) 추가

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
    "iot:Publish",
    "iot:Connect",
    "iot:Receive",
    "iot:Subscribe"
],
"Resource": ""
},
{
"Effect": "Allow",
"Action": [
    "s3:PutObject"
],
    "Resource": "arn:aws:s3:::iot-image-bucket/"
    }
    ]
}

“Create” 클릭

“Secure” → “Certificates”에서 Attach policy 선택 후 IoT_MQTT_S3_Policy 정책을 연결

그림 85

정책

그림 85 정책


AWS Lambda 설정

Lambda 함수 생성

AWS Lambda 콘솔로 이동한다.

“Create function” 클릭

Function name: iot-image-handler

Runtime: Python 3.8 이상 선택

“Create function” 클릭

Lambda에 S3 접근 권한 추가

Lambda 콘솔에서 “Configuration” → “Permissions” 클릭

“Execution role” 아래 “Role name” 클릭

IAM 콘솔에서 “Add permissions” → “Attach policies” 클릭

“AmazonS3FullAccess” 선택 후 “Attach policies” 클릭


Lambda 코드 작성

AWS IoT Core에서 MQTT 메시지를 받아 S3에 저장하는 Python 코드

 import boto3
 import base64
 import json
 import time


 s3 = boto3.client("s3")
 BUCKET_NAME = "iot-image-bucket"

 def lambda_handler(event, context):
 try:
 print("📡 수신된 이벤트:")
 print(json.dumps(event, indent=4))

     image_data_base64 = event.get("image_data", None)
     if not image_data_base64:
         raise ValueError("❌ 'image_data' 필드가 이벤트에 없음!")

    image_data = base64.b64decode(image_data_base64)
    filename = f"image_{int(time.time())}.png"

    s3.put_object(
        Bucket=BUCKET_NAME,
        Key=filename,
        Body=image_data,
        ContentType="image/png"
    )

    print(f"✅ S3에 이미지 업로드 완료: {filename}")
    return {"statusCode": 200, "body": json.dumps(f"Image uploaded to S3: {filename}")}
except Exception as e:
    print(f"❌ 오류 발생: {e}")
    return {"statusCode": 500, "body": json.dumps(f"Error: {str(e)}")}

AWS IoT Core “Rules” 설정

AWS IoT Core에서 “Message routing” → “Rules” 선택

기존의 iot-image-rule 클릭

SQL Statement 수정

SELECT * FROM 'image/topic'

“Lambda” 액션 추가

Lambda 함수 iot-image-handler 선택

“Update rule” 클릭하여 저장

그림 86 규칙

규칙

그림 86 규칙


Python 코드 작성 (MQTT로 이미지 전송)

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import base64
import time
import json

ENDPOINT = "a263uq00h584cr-ats.iot.us-east-1.amazonaws.com"
THING_NAME = "iot-image-uploader"
TOPIC = "image/topic"

CA_PATH = "C:\Users\dlwng\Downloads\AmazonRootCA1.pem"
CERT_PATH = "C:\Users\dlwng\Downloads\device-certificate.pem.crt"
KEY_PATH = "C:\Users\dlwng\Downloads\private.pem.key"

IMAGE_PATH = "C:\Users\dlwng\Pictures\Screenshots\스크린샷.png"

client = AWSIoTMQTTClient(THING_NAME)
client.configureEndpoint(ENDPOINT, 8883)
client.configureCredentials(CA_PATH, KEY_PATH, CERT_PATH)

client.connect()

with open(IMAGE_PATH, "rb") as image_file:
image_data = base64.b64encode(image_file.read()).decode("utf-8")

payload = json.dumps({"device": THING_NAME, "timestamp": int(time.time()), "image_data": image_data})

client.publish(TOPIC, payload, 1)
client.disconnect()

최종 결과 및 테스트

AWS IoT Core의 MQTT Test Client에서 image/topic을 구독하여 이미지 데이터가 정상적으로 전달되는지 확인한다. S3 버킷에서 이미지가 정상적으로 저장되는지 확인한다.

그림 87 는 결과 사진이다.

MQTT로 수신된 이미지파일

그림 87 MQTT로 수신된 이미지파일


결산

.


기타

.