๐Ÿ˜Ž ๊ณต๋ถ€ํ•˜๋Š” ์ง•์ง•์•ŒํŒŒ์นด๋Š” ์ฒ˜์Œ์ด์ง€?

๋‹ค์‹œ ๋„์ „.. Flask-SocketIO ์‚ฌ์šฉํ•ด์„œ live-graph ๋งŒ๋“ค๊ธฐ (8) ๋ณธ๋ฌธ

๐Ÿ‘ฉ‍๐Ÿ’ป ๋ฐฑ์—”๋“œ(Back-End)/Node js

๋‹ค์‹œ ๋„์ „.. Flask-SocketIO ์‚ฌ์šฉํ•ด์„œ live-graph ๋งŒ๋“ค๊ธฐ (8)

์ง•์ง•์•ŒํŒŒ์นด 2022. 12. 2. 10:36
728x90
๋ฐ˜์‘ํ˜•

<๋ณธ ๋ธ”๋กœ๊ทธ๋Š” donskytech ๋‹˜์˜ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ๊ณต๋ถ€ํ•˜๋ฉฐ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค :-)>

https://www.donskytech.com/python-flask-websockets/

 

Display Real-Time Updates Using Python, Flask, and Websockets

We will discuss how to display a real-time application using Python, Flask, and Websocket using the Flask-SocketIO library.

www.donskytech.com

 

 

๐Ÿ”ฅ Flask-SocketIO

Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ์งง์€ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์— ์•ก์„ธ์Šค

ํด๋ผ์ด์–ธํŠธ ์ธก ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Javascript, Python, C++, Java ๋ฐ Swift ์˜ ๋ชจ๋“  SocketIO ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋˜๋Š” ๊ธฐํƒ€ ํ˜ธํ™˜ ๊ฐ€๋Šฅํ•œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„์— ๋Œ€ํ•œ ์˜๊ตฌ ์—ฐ๊ฒฐ์„ ์„ค์ •

 

SocketIO

  • ์‹ค์ œ ์ „์†ก ํ”„๋กœํ† ์ฝœ์—์„œ ํด๋ผ์ด์–ธํŠธ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์ถ”์ƒํ™”ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ € ๊ฐ„ Javascript ๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.

 

โœ” flask์—์„œ socket ๋งŒ๋“ค๊ธฐ

๋”๋ณด๊ธฐ

Flask-SocketIO ์ฐธ๊ณ 

https://bokyeong-kim.github.io/python/flask/2020/05/09/flask(1).html

socketio = SocketIO(app)
  • SocketIO๋Š” ‘์•ฑ’์— ์ ์šฉ๋˜๊ณ  ์žˆ์œผ๋ฉฐ ๋‚˜์ค‘์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•  ๋•Œ ์•ฑ ๋Œ€์‹  socketio๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก socketio ๋ณ€์ˆ˜์— ์ €์žฅ
  • socketio๋Š” ์›น ์„œ๋ฒ„๋ฅผ ์บก์Šํ™”

 

@app.route('/')
def sessions():
    return render_template('session.html')
  • ํ™ˆํŽ˜์ด์ง€ (๊ฒฝ๋กœ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ‘/’๋กœ ํ‘œ์‹œ)๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด ๊ทธ ์•„๋ž˜์— ์„ ์–ธ ๋œ ์„ธ์…˜๋ณด๊ธฐ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ
    • render_template : HTML ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋ง, ํ…œํ”Œ๋ฆฟ ํด๋”์— ํฌํ•จ
  • ์–ธ๊ธ‰ ๋œ URL์„ ์—ด ๋•Œ ํ˜ธ์ถœ

 

โœ” Flask-Socket Handling

  • ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๋Š” ๋ฐฉ๋ฒ•
  • ํด๋ผ์ด์–ธํŠธ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ํด๋ผ์ด์–ธํŠธ์—์„œ WebSocket ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•˜๊ธฐ ์œ„ํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ socketio.on ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ(@socketio.on())๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ •์˜
  • send() ๋ฐ emit() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Œ
@socketio.on('my event')

# json ๊ฐ์ฒด ์ˆ˜์‹  -> myresponse ์ด๋ฒคํŠธ๋กœ ์ „์†ก
def handle_my_custom_event(json, methods=['GET', 'POST']):
    print('received my event: ' + str(json))
    # callback -> ์„œ๋ฒ„์— ์˜ํ•ด ๋ฉ”์‹œ์ง€์˜ ์ˆ˜์‹  ์—ฌ๋ถ€ ๋„์™€์ฃผ๋Š” ๋””๋ฒ„๊น…
    socketio.emit('my response', json, callback=messageReceived)

 

โœ” ์—ฐ๊ฒฐ ์„ค์ • ๋ฐ ์„ธ์…˜ ์ƒ์„ฑ

var socket = io.connect('http://' + document.domain + ':' + location.port)
  • io.connect()๋ฅผ ์‚ฌ์šฉ
  • ๋‘ ์‚ฌ์šฉ์ž ๋ชจ๋‘ ๋™์ผํ•œ URL์— ์—ฐ๊ฒฐํ•˜์—ฌ ์„ธ์…˜ ์ƒ์„ฑ
  • document.domain์€ ์ž‘์—… ์ค‘์ธ ์ปดํ“จํ„ฐ์˜ IP ์ฃผ์†Œ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • location.port๋Š” ํฌํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๊ธฐ๋ณธ๊ฐ’์€ 5000

 

 

โœ” ์ด๋ฒคํŠธ

socket.on( 'connect', function() {
  socket.emit( 'my event', {
    data: 'User Connected'
  })
  var form = $( 'form' ).on( 'submit', function( e ) {
    e.preventDefault()
    let user_name = $( 'input.username' ).val()
    let user_input = $( 'input.message' ).val()
    socket.emit( 'my event', {
      user_name : user_name,
      message : user_input
    })
    $( 'input.message' ).val( '' ).focus()
  })
})
  • socket.on()์— ๋Œ€ํ•œ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” ์ด๋ฒคํŠธ ์ด๋ฆ„
    • ‘connect’, ‘disconnect’, ‘message’, ‘json’์€ socketIO์— ์˜ํ•ด ์ƒ์„ฑ๋œ ํŠน์ˆ˜ ์ด๋ฒคํŠธ
      • ‘message’ : ํ˜•์‹ ๋ฌธ์ž์—ด์˜ ํŽ˜์ด๋กœ๋“œ(payload)๋ฅผ ์ „๋‹ฌ
      • ‘json’ : json ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฒคํŠธ๋Š” ํŒŒ์ด์ฌ ์‚ฌ์ „์˜ ํ˜•ํƒœ๋กœ JSON ํŽ˜์ด๋กœ๋“œ(payload)๋ฅผ ์ „๋‹ฌ
  • send() : ๋ฌธ์ž์—ด ๋˜๋Š” JSON ์œ ํ˜•์˜ ํ‘œ์ค€ ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋ƒ„
  • emit() : ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฒคํŠธ ์ด๋ฆ„(์œ„ ์ฝ”๋“œ์—์„œ๋Š” ‘my event’)์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†ก

 

๐Ÿ”ฅ Chart.js

์ฐจํŠธ ์œ ํ˜•, ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฐ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ต์…˜ ์„ธํŠธ๋ฅผ ์ œ๊ณต

๋‚ด์žฅ TypeScript ํƒ€์ดํ•‘๊ณผ ํ•จ๊ป˜ ์ œ๊ณต๋˜๋ฉฐ React , Vue , Svelte ๋ฐ Angular ๋ฅผ ํฌํ•จํ•˜์—ฌ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“  JavaScript ํ”„๋ ˆ์ž„์›Œํฌ ์™€ ํ˜ธํ™˜

HTML5 ์บ”๋ฒ„์Šค์—์„œ ์ฐจํŠธ ์š”์†Œ๋ฅผ ๋ Œ๋”๋ง

 

 

๐Ÿ”ฅ Web-Socket

๊ฑฐ์˜ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ˆ˜๋‹จ

์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๊ฑฐ์˜ ์—†๋Š” ์–‘๋ฐฉํ–ฅ ๋ฐฉ์‹์œผ๋กœ ์ •๋ณด๋ฅผ ์ „์†กํ•˜๋Š” ์ˆ˜๋‹จ

 

(HTTP ( Hypertext Transfer Protocol )๋ฅผ ์‚ฌ์šฉ ํ•˜๋ฉด ์ „์†ก ์†๋„ ์ธก๋ฉด์—์„œ ์•ฝ๊ฐ„์˜ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒ)

 

Socket

  • ํ•œ ์ปดํ“จํ„ฐ๊ฐ€ ๋‹ค๋ฅธ ์ปดํ“จํ„ฐ์™€ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋กœ ์„ค์ •
  • ๊ฒŒ์ดํŠธ๊ฐ€ ์—ด๋ ค์žˆ์„ ๋•Œ, ์ฆ‰ ์†Œ์ผ“์ด ์—ด๋ ค์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ†ต์‹ ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒŒ์ดํŠธ

 

๐Ÿ”ฅ Flask

์›น ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ‘œ์‹œํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ๋ฌผ ์ธํ„ฐ๋„ท(IOT) ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” "๋งˆ์ดํฌ๋กœ" ์›น ํ”„๋ ˆ์ž„์›Œํฌ

HTML ํŽ˜์ด์ง€ ํ‘œ์‹œ or JSON (Javascript Object Notation) ๋ฐ˜ํ™˜

 

 

๐Ÿ”ฅ ์ฝ”๋“œ ๊ตฌํ˜„

 

 

  • app.py
from flask import Flask, render_template, request
from flask_socketio import SocketIO
from random import random
from threading import Lock
from datetime import datetime
import pandas as pd

"""
Background Thread
"""
thread = None
thread_lock = Lock()

app = Flask(__name__)
app.config['SECRET_KEY'] = 'gani!'
socketio = SocketIO(app, cors_allowed_origins='*')

"""
Get current date time
"""
def get_current_datetime(years):
    #now = datetime.now()
    return years.strftime("%m-%d-%Y %H:%M:%S")

"""
Generate random sequence of dummy sensor values and send it to our clients
"""

CNT_WAIT = []
YEARS = []
def data_load() :
    data = pd.read_csv('./data/test.csv')
    
    for i in range(len(data)) :
        CNT_WAIT.append(int(data["cnt_wait"][i]))
        YEARS.append(data["insert_date_time"][i])

    return CNT_WAIT, YEARS

def background_thread():
    print("Generating random sensor values")
    CNT_WAIT_data, YEARS_data = data_load()
    #YEARS_data = get_current_datetime(YEARS_data)
    cnt = 0
    while True:
        # ๋žœ๋ค ์ƒ์ˆ˜๋ฅผ value : ์˜†์— ๋„ฃ์œผ๋ฉด ๋žœ๋ค ๊ทธ๋ž˜ํ”„ ์™„์„ฑ
        # dummy_sensor_value = round(random() * 100, 3)
        print(CNT_WAIT_data[cnt])
        print(YEARS_data[cnt])
        # print(dummy_sensor_value)
        
        socketio.emit('updateSensorData', {'value': CNT_WAIT_data[cnt], "date": YEARS_data[cnt]})
        cnt += 1
        socketio.sleep(1)


"""
Serve root index file
"""
@app.route('/')
def index():
    return render_template('index.html')

"""
Decorator for connect
"""
@socketio.on('connect')
def connect():
    global thread
    print('Client connected')

    global thread
    with thread_lock:
        if thread is None:
            thread = socketio.start_background_task(background_thread)

"""
Decorator for disconnect
"""
@socketio.on('disconnect')
def disconnect():
    print('Client disconnected',  request.sid)

if __name__ == '__main__':
    socketio.run(app)

 

  • template/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Real Time Sensor Display Using Python, Flask, Flask-SocketIO</title>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
    <link
      href="//mincss.com/entireframework.min.css"
      rel="stylesheet"
      type="text/css"
    />
    <link href="{{url_for('static', filename = 'css/app.css')}}" rel="stylesheet">
  </head>
  <body>
    <script type="text/javascript" src="{{ url_for('static', filename = 'js/app.js') }}"></script>

    <nav class="nav" tabindex="-1" onclick="this.focus()">
      <div class="container">
        <a class="pagename current" href="#">๋ฐฐ๊ณ ํ”„๋‹น๋‹น๊ตฌ๋ฆฌ</a>
      </div>
    </nav>
    <button class="btn-close btn btn-sm">×</button>
    <div class="container">
      <div class="hero">
        <h1>Real Time Graph Display</h1>
        <div class="chart-container">
          <canvas id="myChart" width="1000" height="600"></canvas>
        </div>
      </div>
    </div>
  </body>
</html>

 

  • js/app.js
$(document).ready(function () {
    const ctx = document.getElementById("myChart").getContext("2d");
  
    const myChart = new Chart(ctx, {
      type: "line",
      data: {
        datasets: [{ label: "Temperature",  }],
      },
      options: {
        borderWidth: 3,
        borderColor: ['rgba(255, 99, 132, 1)',],
      },
    });
  
    function addData(label, data) {
      myChart.data.labels.push(label);
      myChart.data.datasets.forEach((dataset) => {
        dataset.data.push(data);
      });
      myChart.update();
    }
  
    function removeFirstData() {
      myChart.data.labels.splice(0, 1);
      myChart.data.datasets.forEach((dataset) => {
        dataset.data.shift();
      });
    }
  
    const MAX_DATA_COUNT = 10;
    //connect to the socket server.
    //   var socket = io.connect("http://" + document.domain + ":" + location.port);
    var socket = io.connect();
  
    //receive details from server
    socket.on("updateSensorData", function (msg) {
      console.log("Received sensorData :: " + msg.date + " :: " + msg.value);
  
      // Show only MAX_DATA_COUNT data
      if (myChart.data.labels.length > MAX_DATA_COUNT) {
        removeFirstData();
      }
      addData(msg.date, msg.value);
    });
  });

 

  • css/app.css
.hero {
    background: #eee;
    padding: 20px;
    border-radius: 10px;
    margin-top: 1em;
}

.hero h1 {
    margin-top: 0;
    margin-bottom: 0.3em;
    text-align: center;
}


.chart-container {
	max-width: 800px;
	margin: 0 auto;
}

 

 

728x90
๋ฐ˜์‘ํ˜•
Comments