Lora 일지 2

KyoungJae Jung ·2022년 2월 18일
0

이전의 마무리에서 프로젝트를 진행하기 앞서 End to End통신을 테스트하고 End to Gateway를 진행할 것이다.

일단 Lora를 통한 End to End 통신을 하기 위한 FSM의 설계는 아래와 같습니다.


3가지의 상태를 정의하고 이 상태를 돌며 통신을 하는 기본적인 FSM을 통해 End to End 통신을 기획하고 코드를 작성하였습니다.

일단 IDLE상태에 대한 정의입니다.

 case MAINSTATE_IDLE: //IDLE state description
                
                if (arqEvent_checkEventFlag(arqEvent_dataRcvd)) //if data reception event happens
                {
                    //Retrieving data info.
                    uint8_t srcId = arqLLI_getSrcId();
                    uint8_t* dataPtr = arqLLI_getRcvdDataPtr();
                    uint8_t size = arqLLI_getSize();

                    pc.printf("\n -------------------------------------------------\nRCVD from %i : %s (length:%i, seq:%i)\n -------------------------------------------------\n", 
                                srcId, arqMsg_getWord(dataPtr), size, arqMsg_getSeq(dataPtr));

                    main_state = MAINSTATE_TX;
                    flag_needPrint = 1;

                    //arqEvent_clearEventFlag(arqEvent_dataRcvd);
                }

                else if (arqEvent_checkEventFlag(arqEvent_dataToSend)) //if data needs to be sent (keyboard input)
                {
                    //msg header setting
                    pduSize = arqMsg_encodeData(arqPdu, originalWord, seqNum, wordLen);
                    arqLLI_sendData(arqPdu, pduSize, dest_ID);
                    seqNum++;
                    pc.printf("[MAIN] sending to %i (seq:%i)\n", dest_ID, (seqNum-1)%ARQMSSG_MAX_SEQNUM);

                    main_state = MAINSTATE_TX;
                    flag_needPrint = 1;

                    //wordLen = 0;
                    //arqEvent_clearEventFlag(arqEvent_dataToSend);
                }
                

                else if (flag_needPrint == 1)//IDLE 상태에서 나오는 입력창
                {
                    pc.printf("Give a word to send : ");
                    flag_needPrint = 0;
                }     

                break;

이부분은 2가지 경우를 처리합니다.
PDU가 들어와 ACK를 보내야 하는 경우나 SDU를 가공해 PDU를 보내야 하는 상황입니다.
따라서 PDU가 들어왔을 시에는 이벤트 발생을 체크해 데이터가 들어왔을 경우 TX로 상태를 변경합니다.
또는 SDU를 PDU로 가공해 전송해야 할 시에는 이벤트 체크를 통해 보낼 데이터가 있다면 이를 상대방에게 송신을 하고 sequence번호를 증가시킨 후 TX단계로 넘어갑니다.

다음은 TX구간입니다.

case MAINSTATE_TX: //IDLE state description

                //PDU 수신시 ACK전송
                if (arqEvent_checkEventFlag(arqEvent_dataRcvd))
                {
                    if (retxCnt != 0)//TX중 새로운 노드가 와 PDU를 전송해 이를 처리하는 부분,
                    {
                        uint8_t nsrcId = arqLLI_getSrcId();
                        uint8_t* dataPtr = arqLLI_getRcvdDataPtr();
                        uint8_t size = arqLLI_getSize();
                        ackSize = arqMsg_encodeAck(arqAck, seqNum);
                        arqLLI_sendData(arqAck, ackSize, nsrcId);
                        pc.printf("RCVD from %i : %s (length:%i, seq:%i)\n", nsrcId, arqMsg_getWord(dataPtr), size, arqMsg_getSeq(dataPtr));
                        pc.printf("ACK is Correctly Send to %i\n", nsrcId);
                        
                        arqEvent_clearEventFlag(arqEvent_dataRcvd);
                        arqEvent_clearEventFlag(arqEvent_ackTxDone);//  TX전송 중 들어온 노드의 데이터 처리후 TXdone을 하기에 클리어를 하여 기존의 작업 마무리 지시.
                        main_state = MAINSTATE_ACK;
                    }
                    else //일반적인 TX 재전송
                    {
                        
                        ackSize = arqMsg_encodeAck(arqAck, seqNum);
                        arqLLI_sendData(arqAck, ackSize, dest_ID);
                        pc.printf("ACK is Correctly Send to %i\n", dest_ID);
                        main_state = MAINSTATE_IDLE;
                        arqEvent_clearEventFlag(arqEvent_dataRcvd);
                        arqEvent_clearEventFlag(arqEvent_ackTxDone);
                    }
                }

                //SDU 수신시 타이머 시작과 ACK으로 변경
                else if (arqEvent_checkEventFlag(arqEvent_dataToSend))
                {
                    arqTimer_startTimer();
                    main_state = MAINSTATE_ACK;
                    arqEvent_clearEventFlag(arqEvent_dataToSend);
                }

                //Time out이 났을 경우 재전송.
                else if (arqEvent_checkEventFlag(arqEvent_arqTimeout))
                {
                    arqTimer_startTimer();
                    arqLLI_sendData(arqPdu, pduSize, dest_ID);
                    pc.printf("[MAIN] sending to %i (seq:%i)\n", dest_ID, (seqNum - 1) % ARQMSSG_MAX_SEQNUM);
                    main_state = MAINSTATE_ACK;
                    arqEvent_clearEventFlag(arqEvent_arqTimeout);
                }

                
                else if (arqEvent_checkEventFlag(arqEvent_dataTxDone)) //data TX finished
                {
                    main_state = MAINSTATE_IDLE;
                    arqEvent_clearEventFlag(arqEvent_dataTxDone);
                }

                break;

우리는 PDU 수신 시 이벤트 체크를 통해 데이터를 받았다면 ACK를 보내주어야 합니다.
하지만 이에는 예외적인 상황이 있을 수 있습니다.
TX전달하고 ACK를 기다리는 중 새로운 노드가 난입하여 DATA REQ를 보낼 경우를 생각해야 합니다.
ACK 대기 도중 새로운 노드가 들어올 것이라 가정하고 retxCnt를 통해 이벤트를 처리했습니다.
왜냐하면 기존 노드는 원하던 노드와 통신하는 도중이면 retxCnt가 증가해 있을 것입니다.
만약 이 상황에 새로운 노드가 난입을 했다면 이 상황을 걸려줄 방법은 retxCnt로 확인해주는 방법밖에는 없습니다.
새로 들어온 노드를 정의하고 ACK를 보내면 기존의 노드는 Timeout이 될 것이고 새로 온 노드를 처리하면 TXdone이되므로 이를 클리어해 다시 기존의 노드에게 돌아가 Timeout을 처리하고 ACK를 전송합니다.
그 다음은 IDLE에서 SDU가 들어올 경우 전송을 하고 TX로 바꿧기에 TX에서 timer를 시작하고 상태를 ACK로 변경시킵니다.
다음은 Timeout이 났을 경우 다시 전송하고 타이머를 키고 상태를 ACK로 보냅니다.
만약 전송이 끝났다면 상태를 IDLE로 변경합니다

다음은 ACK의 상태입니다.

case MAINSTATE_ACK:

                //보낼 데이터가 있는 경우 TX로 상태 변경
                if (arqEvent_checkEventFlag(arqEvent_dataRcvd)) 
                {
                    main_state = MAINSTATE_TX;
                }

                //ACK를 받았을 경우 타이머 중지 및 처리함수
                else if (arqEvent_checkEventFlag(arqEvent_ackRcvd)) 
                {
                    arqTimer_stopTimer();
                    retxCnt = 0;
                    
                    main_state = MAINSTATE_IDLE;
                    pc.printf("ACK is correctly received\n");
                    wordLen = 0;
                    arqEvent_clearEventFlag(arqEvent_ackRcvd);
                }

                //ACK이 시간내에 전송이 되지 않았을 경우 처리부분
                else if (arqEvent_checkEventFlag(arqEvent_arqTimeout)) 
                {
                    arqTimer_stopTimer();
                    if (retxCnt >= ARQ_MAXRETRANSMISSION) // 최대 전송보다 높을 경우 연결 끊기
                    {
                        pc.printf("Re Transmission is MAX so Disconnect!\n");
                        main_state = MAINSTATE_IDLE;
                        retxCnt = 0;
                        wordLen = 0;
                        arqEvent_clearEventFlag(arqEvent_arqTimeout);
                    }
                    else if (retxCnt < ARQ_MAXRETRANSMISSION) // 최대 전송보다 낮다면
                    {
                        retxCnt++;
                        main_state = MAINSTATE_TX;
                        pc.printf("timeout! retransmit\n");
                        //arqEvent_clearEventFlag(arqEvent_arqTimeout);
                        arqEvent_clearEventFlag(arqEvent_dataToSend);
                    }

                }

                else if (arqEvent_checkEventFlag(arqEvent_ackTxDone)) // ACK 보냈다면 IDLE로 복귀
                {
                    main_state = MAINSTATE_IDLE;
                    arqEvent_clearEventFlag(arqEvent_ackTxDone);
                }

ACK부분에서 보낼 데이터가 있다면 TX로 상태를 변경합니다.
즉 ACK에서 Data In일경우 Data REQ입니다.
다음은 ACK를 받았다면 Timer를 Stop하고 재전송 카운터나 단어크기를 초기화해서 IDLE상태로 변경합니다.
만약 ACK가 Time내에 들어오지않았다면 만약 재전송횟수가 MAX보다 작다면 재전송 아니면 그 연결을 해제합니다. 연결해체의 의미는 전송을 더 이상 하지 않음을 이야기합니다.
그리고 ACK이 마무리가 되었다면 IDLE로 복귀합니다.

이러한 형식으로 End to End 통신을 설계하고 진행을 해봤습니다.
모든 설계에서 Data를 보내고 확인받는 형식으로 동작하는 형식으로 작성되었습니다.

실제 테스트 화면

  • 송신(일반적인)
  • 수신(일반적인)
  • 송신(Drop율 100)
  • 수신
  • ARQ서버와 통신 중 새로운 노드의 난입

이 다음은 실제 End to Gateway 연결을 진행해 볼 것입니다.

profile
보안전문가를 꿈꾸는 대학생

0개의 댓글