전자공학/MCU

[아두이노]Serial 통신3 (Flask 활용)

17Hyuk 2022. 5. 8. 17:16

이번에는 Flask를 활용해서 2편에서 만든코드를 콘솔창이 아닌 웹페이지를 통해 제어할 것이다.

 

우선 우리는 이러한 웹사이트를 통해서 제어할 예정이다.

 

귀찮으면 소스코드 다운받기

LED.zip
0.00MB

 

localhost:5000

입력값 : 1100310051007100

 

 

 

우선 app.py를 통해서 Flask 서버를 실행실킬 것이고, tmeplates 폴더는 render_template를 사용하기 때문에 필요하다.

그리고 LED.ino 를 아두이노에 넣어줘야된다.

 

우선 모듈이 없으면 모듈을 설치해주자

pip install flask
pip install pyserial

 

app.py

from flask import Flask, render_template, request
import serial
app = Flask(__name__)


# input값을 길이 32로 만들어줌
# ex) 51001100 -> 51001100!!!!!!!!!!!!!!!!!!!!!!!! (! 는 아두이노에서 continue 처리)
def len32(tx):
    return str(tx) + ''.join(list('!' for i in range(32-len(tx))))


@app.route('/')
def index():
    return render_template('index.html')

@app.route('/LED_ctrl/', methods=['post'])
def LED_ctrl():
    py_serial = serial.Serial(
        port='COM4',    #본인에게 맞는 포트 설정해주기
        baudrate=9600,
    )
    command = request.form['command']    
    serial_flag = 0
    rx_result = ''

    while True:
        if py_serial.readable():
            rx = py_serial.readline()[:-1].decode()
            print(rx)
            rx_result = rx_result + rx + '\n'

        # 명령어 전송
        if serial_flag==0 :
            tx = len32(command)
            py_serial.write(tx.encode())
            serial_flag = 1

        if rx[0:9] == 'Complete!':
            break
    return render_template('index.html', result = rx_result)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)

 

설명

더보기

우선 def LED_ctrl 를 보면 우선 py_serial를 통해서 serial 통신을 할 준비를 해준 후

(이 코드를 밖에 빼면 좋았겠지만 이유는 잘 모르겟지만 액세스가 거부 오류가 떠서 안으로 넣어줬다)

command : html의 input박스에서 넣어준 값을 request.form['command']를 통해 할당시켜줬다.

serial_flag : 입력을 한번만 받기 위해서 만들어준 변수이다.

rx_result : 나중에 출력값을 webpage에 출력해주기 위해서 만들어준 문자열이다.

while True를 통해서 무한루프를 돌면서 출력값을 rx_result로 받아준 후

if문을 통해서 'Complete!'가 rx에 들어가게 되면 무한 루프를 종료 시켜줬다.

 

이 코드의 경우는 Flask를 처음 접하면 어려울 수 도 있는데

render_template 는 webpage에 html 파일을 출력시켜주는 함수이다. 그래서 index.html 를 출력시켜준다.

html 파일은 반드시 templates 폴더 안에 있어야한다.

그리고 콤마(,) 뒤에 result=rx_result는 왼쪽의 result는 html 내에서 사용하는 변수이고 그 값을 할당시켜주는 것 이다.

아래 index.html 코드를 보면 {{ result }} 라는 코드가 있는데 이 곳에 rx_result를 대입시켜주는 것 이다.

 

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Serial_17eehyuk</title>
        <script>
            function num_only(this_plz){
                this_plz.value = this_plz.value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1');
            }
        </script>
    </head>
    <body>
        <h2>Serial 통신3(17eehyuk)</h2>        
        <form method="post">
            <input type="text" name="command" maxlength="32" oninput="num_only(this)" required>
            <input type="submit" value="전송" formaction="/LED_ctrl/">
        </form>
        
        <pre>{{ result }}</pre>     {# 출력을 그대로 받기 위해서 pre 태그로 감싸줌 #}

    </body>
</html>

설명

더보기

num_only : 숫자만 입력받을 수 있는 함수이다.

'this_plz' 라고 한 이유는 'this'를 쓰면 좋았겠지만, this는 매개변수명으로 사용할 수 없으므로 'this_plz' 라고 작성했다.

그 외에는 form을 통해서 command를 전송시켜주는 간단한 코드이다.

LED.ino

void setup() {
  pinMode(2, OUTPUT);pinMode(3, OUTPUT);pinMode(4, OUTPUT);pinMode(5, OUTPUT);  
  pinMode(6, OUTPUT);pinMode(7, OUTPUT);pinMode(8, OUTPUT);pinMode(9, OUTPUT);
  
  Serial.begin(9600);
  
}


void loop() {
  Serial.print("Serial Start!\n");
  
  while(Serial.available()==0){
  }

  String rx = Serial.readString();
  
  for(int i=0; i<8; i++){
    if(rx[4*i]=='!'){continue;}
    char led = rx[4*i]-'0'+2;   //문자 -> 숫자 (+2는 내가 핀을 2번부터 시작하기 때문)
    int opt = 10*(100*(rx[4*i+1]-'0')+10*(rx[4*i+2]-'0')+1*(rx[4*i+3]-'0'));  //operating time

    //시리얼 메세지
    char msg[40];
    sprintf(msg, "%d번 LED %d ms", led-2, opt);    //메세지는 -2 해줘야 정확하게 출력
    Serial.println(msg);

    //LED 작동
    digitalWrite(led, HIGH);
    delay(opt);
    digitalWrite(led, LOW);
  };

  Serial.print("Complete!\n");

}

아두이노는 2편과 동일하므로 2편코드를 참조바란다.(https://17eehyuk.tistory.com/24)

 

 

 

약간의 아쉬운점이 있다면 결과가 출력될 때 까지 페이지가 멈추는 점이고(동기식이기 때문)

비동기처리를 통해서 결과를 한줄씩 출력해주면 좋겠지만

나는 아직 ajax를 어떻게 다루는지 모르기 때문에 기회가 되면 4편에서 좀더 예쁘게 만들겠다.