본문 바로가기

프로젝트형 IoT 서비스 개발 4회차/3. 게이트웨이 디바이스 제어

[Day57] 2022-04-19(화) 라즈베리파이6 - Thread2, WebSocket1 - 김서연 강사님

728x90

[1] Thread (어제 수업 이어서)

https://powerstone829.tistory.com/82

 

[Day56] 2022-04-18(월) 라즈베리파이5 - Thread - 김서연 강사님

[1] Thread - threading 모듈을 이용해서 작업 1. 파이썬에서 threading 모듈을 사용하는 방법1 - Thread 클래스 객체 생성 1) 멀티쓰레드로 즉, 독립적인 실행흐름을 갖고 실행하고 싶은 코드를 함수로 정의

powerstone829.tistory.com

  1. Thread 클래스 상속 이용 예제

    1) 예제1

# threading_inheritance_exam1.py
# Thread 클래스 상속받아서 클래스 작성
# A : 65, Z : 90
# - 두 개의 클래스를 만들어서 쓰레드를 생성한다.
# - AlphaThread 클래스
#   - printAlpha메소드를 정의하고, A......Z까지 출력
# - DigitThread 클래스
#   - printDigit 메소드를 정의하고, 1....30까지 출력

from threading import Thread
import time

class AlphaThread(Thread):
    def __init__(self):
        Thread.__init__(self)
    
    def run(self):
        for i in range(65, 91):
            print(chr(i))
            time.sleep(0.5)

class DigitThread(Thread):
    def __init__(self):
        Thread.__init__(self)
    
    def run(self):
        for i in range(1, 31):
            print('\t', i)
            time.sleep(0.5)


if __name__ == "__main__":
    alpha = AlphaThread()
    digit = DigitThread()
    
    print("Main Thread Start")
    alpha.daemon = True     # 이렇게 세팅하면 메인쓰레드 종료하면 생성된 쓰레드도 같이 종료
    digit.daemon = True
    alpha.start()
    digit.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Main Thread End")

※ crtl + c 입력하면 메인 쓰레드는 종료되지만, 생성된 쓰레드는 종료되지 않는 문제 해결

  - Thread객체.daemon = True

    - 메인 쓰레드가 종료되면 생성된 쓰레드가 자동으로 종료

    - while문 등으로 메인 쓰레드가 계속 돌 수 있도록 해줘야 한다.

https://stackoverflow.com/questions/11815947/cannot-kill-python-script-with-ctrl-c

 

Cannot kill Python script with Ctrl-C

I am testing Python threading with the following script: import threading class FirstThread (threading.Thread): def run (self): while True: print 'first' class SecondThre...

stackoverflow.com


    2) 예제2

# threading_inheritance_test2.py
from threading import Thread
import requests
import time

class Html(Thread):
    def __init__(self, url):
        Thread.__init__(self)
        self.url = url
    
    def run(self):
        response = requests.get(self.url)
        time.sleep(1)
        print(self.url, len(response.text), response.text)
        print("test.......")
    

# getHtml 함수를 멀티쓰레드로 실행되도록 처리

t1 = Html('https://www.naver.com')
t1.start()  # 쓰레드를 생성해서 적절한 시점에 호출(스케줄러에 의해)
time.sleep(1.2)
print("프로그램 종료+++++++++++++++++++++++")

    3) 예제3

# threading_led_inheritance_exam.py
# - Main Thread : "Main Thread"를 1초에 한 번씩 출력하는 주 쓰레드
# - Thread 클래스 : 1초에 LED의 전구가 on/off가 반복되도록 쓰레드 작성하기(함수명 : blink_led)

import RPi.GPIO as gpio
from threading import Thread
import time

led_pin = 22
gpio.setmode(gpio.BCM)
gpio.setup(led_pin, gpio.OUT)

class Led(Thread):
    def __init__(self):
        Thread.__init__(self)
        
    def run(self):
        print("blink_led 시작")
        try:
            while True:
                gpio.output(led_pin, gpio.HIGH)
                time.sleep(0.5)
                gpio.output(led_pin, gpio.LOW)
                time.sleep(0.5)
        except RuntimeError:
            pass
        finally:
            print("blink_led 종료")


ledThread = Led()
ledThread.start()
print("Main Thread 시작")
try:
    while True:
        print("main쓰레드")
        time.sleep(1)
except KeyboardInterrupt:
    pass
finally:
    gpio.cleanup()
    print("Main Thread 종료")

  2. 동기화

    - 멀티 쓰레드 프로그래밍을 할 때 쓰레드들이 공유자원에 동시에 접근하는 경우 무결하지 못한 데이터가 발생

      - 공유 자원 : 모든 쓰레드가 사용하는 객체

      - 무결하지 못한 데이터 : 신뢰할 수 없는 데이터

    - 임계영역(Critical section) : 공유 데이터 접근해서 사용하는 코드

    - 문제 해결을 위한 방법 중 상호배제 사용

      - 두 개 이상의 쓰레드가 임계영역에 진입하지 못하도록 하는 것

        -> Lock 객체를 이용해서 상호 배제를 처리

        -> 한 쓰레드가 공유객체를 점유해서 사용하는 동안 공유객체에 lock을 걸어서 접근하지 못하도록 처리

    - 적용 방법

      ① 공유객체 내부에서 Lock 객체를 생성

      ② 공유해서 사용하는 부분의 전에 Lock객체의 acquire( )를 호출

        - 한 쓰레드만 소유하고 다른 쓰레드는 대기 상태에 있을 수 있도록 작업

      ③ 소유해서 사용하던 Lock을 해제 : release( )

        - 대기 상태에 있는 쓰레드가 공유객체를 점유하고 lock객체를 획득

 

    1) 동기화가 되지 않는 경우 예제

# toilet.py
import threading

# 모든 객체가 공유해서 사용하는 객체
class Toilet:
    def __init__(self):
        pass
    
    # 공유해서 사용할 기능이 정의된 메소드
    def open(self, name):
        print(name+"이 문열고 들어옴")
        for i in range(100000000):
            if i == 10000:
                print(name+"이 끙~~~~~~~~아~~~~~~~~")
        print(name+"이 문열고 나감")
# user.py
import string
from threading import Thread

class User(Thread):
    def __init__(self, name:string, toilet:object):   # toilet 객체는 사용자 쓰레드가 공유해서 사용할 객체
        super().__init__()
        self.name = name
        self.toilet = toilet
        
    def run(self):      # 쓰레드로 실행되어지는 run메소드에서 공유객체를 사용
        self.toilet.open(self.name)
# toilet_test.py
from toilet import Toilet
from user import User

print("작업 시작")

# 공유객체 만들기
toilet = Toilet()

# 쓰레드 객체를 7개 생성해서 작업해보기
User("RM", toilet).start()
User("제이홉", toilet).start()
User("슈가", toilet).start()
User("진", toilet).start()
User("뷔", toilet).start()
User("정국", toilet).start()
User("지민", toilet).start()

    2) 동기화 사용 예제

      - 위의 코드에서 toilet.py 만 수정해주면 된다.

# toilet.py
from threading import Lock

# 모든 객체가 공유해서 사용하는 객체
class Toilet:
    def __init__(self):
        self.lock = Lock()
    
    # 공유해서 사용할 기능이 정의된 메소드
    def open(self, name):
        # Lock객체의acquire메소드를 호출하면 Lock객체를 사용하는 쓰레드가 점유(쓰레드가 점유해서 Toilet객체의 open메소드를 사용하는 동안 다른 쓰레드는 호출하지 못하고 대기 상테에 있을 수 있도록 설정)
        self.lock.acquire()
        print(name+"이 문열고 들어옴")
        for i in range(1000000):
            if i == 10000:
                print(name+"이 끙~~~~~~~~아~~~~~~~~")
        print(name+"이 문열고 나감")
        self.lock.release()     # 한 쓰레드에서 작업이 끝나면 lock을 해제

[2] Web 에서 MQTT와 웹소켓 활용

  - front 페이지에서 web과 broker의 통신은 sub/pub가 지속되어야 한다.

    -> 즉, 실시간으로 subscribe/publish할 수 있어야 한다.

  - 이러한 작업은 websocket 통신을 이용해서 작업해야 한다.

  - 본래 websocket 통신을 구현하려면 복잡한 코드 작성이 필요하다.

  - 하지만, mosquitto(broker)가 websocket 통신을 지원한다.

    -> 따라서, back-end에서 websocket통신을 위한 코드를 직접 작성하지 않아도 된다.

  1. Django 환경 세팅

https://powerstone829.tistory.com/26?category=1006501 

 

[Day18] 2022-02-21(월) Web Application 1 - Django 환경 셋팅 - 이진만 강사님

[1] Web Application Settings​ 1. Web Server 2. Python Projcet - Web application 추가 - django Server 3. 개발 순서 1) Python Project 생성 2) 서드파트 모듈 설치​ pip 버전 최신버전으로 업그레이드 dja..

powerstone829.tistory.com

  2. 웹소켓 통신 설정

    1) mosquitto의 설정 파일을 변경

      - 기본 설정은 일반적인 MQTT 통신만 지원되도록 설정된 상태

      - 일반적인 MQTT통신과 websocket 통신이 모두 지원되도록 변경 필요(포트가 두 개 열려 있어야 함)

 

      ① mosquitto.conf 파일에 내용 추가

        - 경로> C:\Program Files\mosquitto

mosquitto.conf 를 관리자 권한으로 열기

        - mosquitto.conf 파일을 관리자 권한으로 열어서, 맨 밑에 다음 내용을 추가

맨 밑에 줄에 내용 추가


listener 9001
protocol websockets

#mqtt
listener 1883
protocol mqtt


      ② 명령 프롬프트에서 broker 실행

        - C:\Program Files\mosquitto 로 경로 변경하고 실행(mosquitto.conf 파일 때문)

        명령어> mosquitto -c mosquitto.conf -v

websockets 는 9001 포트, socket 은 1883 포트

    2) websocket 통신을 할 html 페이지에 MQTT JavaScript CDN 추가

<script src = "https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js"
            type = "text/javascript"> </script>

https://www.eclipse.org/paho/index.php?page=clients/js/index.php 

 

Eclipse Paho | The Eclipse Foundation

The Eclipse Foundation - home to a global community, the Eclipse IDE, Jakarta EE and over 415 open source projects, including runtimes, tools and frameworks.

www.eclipse.org

    3) publish 를 위한 함수들 정의

<script type="text/javascript">
    var host = "172.30.1.57";
    var port = 9001;
    var mqtt;

    // callback 함수 - 접속이 완료된 후 호출되는 함수
    function onConnect(){
        console.log("접속완료");
    }
    // callback 함수 - 접속이 실패된 후 호출되는 함수
    function onFailure(){
        console.log("접속실패");
    }

    // publish하는 함수 정의
    function sendMsg(msg){
        // alert(msg);
        /*
            1. message 객체 생성
            2. topic 설정
            3. send 메소드를 호출
        */
        message = new Paho.MQTT.Message(msg);
        message.destinationName = "iot/led";    // 토픽 설정
        // MQTT 메시지 보내기 - publish
        mqtt.send(message);
    }

    // mqtt통신을 관리하기 위한 사용자 정의 함수
    function MQTTConnect(){
        console.log("mqtt접속:"+host+","+port)
        // mqtt 통신을 위한 클라이언트 객체 생성
        mqtt = new Paho.MQTT.Client(host, port, "javascript_client");    // "javascript_client" 클라이언트 ID
        // mqtt 통신을 위해 필요한 설정을 명시
        var options = {
            timeout: 3,
            onSuccess: onConnect,   // 접속 성공 시 실행되는 callback함수 등록
            onFailure: onFailure,
        }
        mqtt.connect(options);
    }
</script>

    4) body 태그 내 코드 - mqtt 접속을 위한 함수 호출

 

<body>
    <script type="text/javascript">
        // 사용자 정의 함수를 호출해서 mqtt가 동작할 수 있도록 해준다.
        MQTTConnect()
    </script>
    <h1>MQTT와 웹소켓 테스트</h1>
    <div id="data"></div>
    <form>
        <input type="button" value="led켜기" onclick="sendMsg('led_on')">
        <input type="button" value="led끄기" onclick="sendMsg('led_off')">
    </form>
</body>

    5) 라즈베리파이에서 subscribe

       - message 받아서 LED on/off 작동하는 코드

# led_Subscriber.py
# 라즈베리파이 subscriber
# message -> led_on : led켜기
# message -> led_off : led끄기
import paho.mqtt.client as client
import RPi.GPIO as gpio

led_pin = 22
gpio.setmode(gpio.BCM)
gpio.setup(led_pin, gpio.OUT)

def connect_result(client, userdata, flags, rc):
    print("connect...", rc)
    if rc == 0:
        client.subscribe("iot/led")
    else:
        print("연결실패")

def on_message(client, userdata, msg):
    message = msg.payload.decode("utf-8")
    print(message)
    if message == "led_on":
        gpio.output(led_pin, gpio.HIGH)
    elif message == "led_off":
        gpio.output(led_pin, gpio.LOW)

try:
    mqttClient = client.Client()
    mqttClient.on_connect = connect_result
    mqttClient.on_message = on_message
    mqttClient.connect("XXX.XXX.XXX.XXX", 1883, 60)
    mqttClient.loop_forever()
except KeyboardInterrupt():
    pass
finally:
    gpio.cleanup()

    6) 웹페이지 접속하여 led_on, led_off 버튼 클릭하여, LED on/off 작동하는지 확인

 

- 끝 -

728x90