본문 바로가기

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

[Day55] 2022-04-15(금) 라즈베리파이4 - MQTT2(paho.mqtt) - 김서연 강사님

728x90

[1] MQTT

  1. Python에서 paho.mqtt 패키지 사용

https://www.eclipse.org/paho/index.php?page=clients/python/docs/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

    1) publish 모듈

      - publish 모듈의 single 메소드 사용

      - 매개변수 : topic, payload(message), hostname(broker IP) 등

      - 메시지 전송만 가능하고, 전송 후에는 연결을 끊는다.

import paho.mqtt.publish as publish

publish.single("iot", "led_on_off", hostname="XXX.XXX.XXX.XXX")

    2) paho.mqtt.client 모듈Client 클래스

출처 - https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

      - publisher, subscriber 모두 사용 가능한 객체를 생성

      - 접속한 상태에서 계속 사용이 가능하며, 토픽이나 메시지를 전송한 후 전송 결과를 리턴해 준다.

      ① 객체 생성

        - 객체 = Client(client_id=""clean_session=Trueuserdata=Noneprotocol=MQTTv311transport="tcp")

출처 - https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

      ② broker 에 접속

        - 객체.connect(host, port=1883, keepalive=60, bind_address="")

출처 - https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

      ③ connect event에 callback 함수 호출

        - def callback함수(client, userdata, flags, rc)

        - 객체.on_connect = callback함수

출처 - https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

    3) Client 클래스로 publish 수행

      ① publish event에 callback 함수 호출

        - def callback함수(client, userdata, mid)

        - 객체.on_publish = callback함수

출처 - https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

      ② publish 실행

        - 객체.publish(topic, payload=None, qos=0, retain=False)

publish 메소드 매개변수 및 return <출처 - https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php>

 

      ③ publisher 예제

# myPublisher_publish.py
import paho.mqtt.client as client       # paho.mqtt 패키지의 client 모듈

# mqttClient객체가 갖고 있는 publish 메소드를 사용 - 객체를 생성
'''
    paho.mqtt.client class의 publish 메소드를 사용하기 위한 방법
    publish는 메시지를 전송하고 다시 결과를 가지고 되돌아온다.
    1. 클라이언트 객체 만들기
    2. 클라이언트가 브로커에 연결하기
    3. 메세지 보내기
    4. 메시지 보낸 후 되돌아오는 결과를 확인
    5. on_publish 콜백을 적용해서 publish가 제대로 됐는지 확인
'''
# 메시지를 전송하고 되돌아왔을 때 실행할 callback 함수 정의
# mid : 메시지 id
def publish_ok(client, userdata, mid):
    print(client, userdata, mid)
    print("데이터 전송")
    
try:
    mqttClient = client.Client("python_pc_pub")
    mqttClient.connect("XXX.XXX.XXX.XXX")   # port=1883 default로 되어 있음
    # 메시지를 전송하고 되돌아왔을 때 발생되는 이벤트가 on_publish이고, 이벤트가 발생되면 callback함수를 실행할 수 있도록 연결
    # 메시지를 보내고 되돌아왔을 때 publish_ok라는 함수를 호출해라
    mqttClient.on_publish = publish_ok
    result = mqttClient.publish("iot/led", "led~~~~~^^")
    print("호출결과=>", result)
    mqttClient.loop(2)  # 2초 동안 기다리는 것
except Exception  as err:
    print("에러:", err)

    4) Client 클래스로 subscribe 수행

      ① subscribe event에 callback 함수 호출

        - def callback함수(client, userdata, message)

        - 객체.on_subscribe = callback함수

      ② subscribe 실행

        - 객체.subscribe(topic, qos=0)

출처 -&nbsp;https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

      ③ message event에 callback 함수 호출

        - def callback함수(client, userdata, message)

        - 객체.on_message = callback함수

출처 -&nbsp;https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php

      ④ subscriber 예제

# mySubscriber.py
import paho.mqtt.client as mqtt    # paho.mqtt 패키지의 client 모듈

# 1. broker에 구독신청 - broker와 연결하고 topic을 등록
# 2. 연결이 성공하면 메시지를 수신하도록 설정
#    연결을 시도한 후 되돌아와서 callback 함수를 호출
# client는 연결했던 정보나 콜백을 실행하는 주체 - 현재 작업하고 있는 publisher의 정보
def connect_result(client, userdata, flags, rc):
    print("connect..."+str(rc))     # rc가 0이면 접속 성공, 1이면 실패
    if rc == 0: # 연결이 성공하면 구독신청
        client.subscribe("iot/#")   # iot/로 토픽이 시작하면 뒤에는 어떤 키워드가 와도 모두 수신
    else:
        print("연결실패....")

def on_message(client, userdata, msg):
    myval = msg.payload.decode("utf-8")
    print(myval)

try:
    mqttClient = mqtt.Client()
    mqttClient.on_connect = connect_result
    mqttClient.on_message = on_message  # 메시지가 broker에서 전달됐을 때 콜백함수가 호출되도록 등록
    mqttClient.connect("XXX.XXX.XXX.XXX", 1883, 60)
    mqttClient.loop_forever()   # 등록한 토픽의 메시지를 broker에서 전송받아야 하므로 대기
except KeyboardInterrupt:
    pass
finally:
    pass

  2. MQTT통신을 이용해 LED 제어

    - subscriber(라즈베리파이) : led_on/led_off 메시지를 받으면 각각 LED를 켜고 끄는 동작을 하도록 작성

    - publisher(PC) : input으로 led_on/led_off를 입력하고, 입력한 내용을 메시지로 전송하도록 작성

    1) subscriber

# 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("172.30.1.57", 1883, 60)
    mqttClient.loop_forever()
except KeyboardInterrupt():
    pass
finally:
    pass

    2) publisher

# led_Publisher.py
import paho.mqtt.client as client

def on_publish(client, userdata, mid):
    print("데이터 전송")
    

try:
    mqttclient = client.Client()
    mqttclient.connect("172.30.1.57", 1883, 60)
    mqttclient.on_publish = on_publish
    while True:
        inputData = input("led_on/led_off 입력: ")
        mqttclient.publish("iot/led", inputData)
        mqttclient.loop(2)
except KeyboardInterrupt():
    pass
finally:
    pass

  3. MQTT통신을 이용해 Servo Motor 제어

    - PC에서 +/-와 각도를 입력 받고, 라즈베리파이로 보내서 Servo Motor의 각도를 변화시키기

    - 0, 90, 180도가 되면,  라즈베리파이에서 PC로 메시지 보내기

    1) PC

Servo_Mqtt_PC_test.py

# Servo_Mqtt_PC_test.py
import paho.mqtt.publish as publish
import paho.mqtt.client as mqtt
def showMenu():
    print("########원하는 작업을 선택하세요############")
    print("1. led상태점검")
    print("2. 서보모터 각도 조절하기(+)")
    print("3. 서보모터 각도 조절하기(-)")
    print("4. 종료하기")

def connect_result(client, userdata, flags, rc):
    print("connect..."+str(rc)) # rc가 0이면 성공 접속, 1이면 실패
    if rc==0 : #연결이 성공하면 구독신청
        client.subscribe("test")
    else:
        print("연결실패.....")
        
def on_message(client, userdata, message):
    myval = message.payload.decode("utf-8")
    print(message.topic+"-----"+myval)
    
            
def degree_publish(degree):
    publish.single("iot/servo",degree,hostname="172.30.1.57")
    
if __name__ =="__main__":
    try:
        mqttClient = mqtt.Client()
        mqttClient.on_connect = connect_result
        mqttClient.on_message = on_message # 메시지가 broker에서 전달됐을때 콜백함수가 호출되도록 등록
        mqttClient.connect("172.30.1.57",1883,60)
        showMenu()
        num = int(input("원하는 작업을 선택하세요"))
        if num==1 :
            led_state = input("led상태를 입력하세요 : ")
            publish.single("iot/led",led_state,hostname="172.30.1.57")
        elif num ==2 :
            servo_degree = input("각도를 입력하세요 : ")
            degree_publish("plus:"+servo_degree)
        elif num ==3:
            servo_degree = input("각도를 입력하세요 : ")
            degree_publish("minus:"+servo_degree)
        else :
            exit(0)
        mqttClient.loop_forever() 
    except KeyboardInterrupt:
        pass
    finally:
        pass

    2) Raspberry PI

Servo_Mqtt_Raspberry_test.py

# Servo_Mqtt_Raspberry_test.py
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publisher
import RPi.GPIO as gpio
import time

gpio.setmode(gpio.BCM)
servo_pin = 26
led_pin = 22
gpio.setup(led_pin,gpio.OUT)
gpio.setup(servo_pin, gpio.OUT)  # 서보핀을 출력으로 설정
pwm_servo = gpio.PWM(servo_pin, 50)  # 서보핀을 PWM 모드 50Hz로 사용
pwm_servo.start(2.5)
angle = 0

def getDuty(degree):
    if degree in (0,90,180):
        publisher.single("iot/return","0, 90, 180",hostname="172.30.1.57")
    duty = 2.5+ degree*10 /180
    return duty


def connect_result(client, userdata, flags, rc):
    print("connect..."+str(rc)) # rc가 0이면 성공 접속, 1이면 실패
    if rc==0 : #연결이 성공하면 구독신청
        client.subscribe("iot/servo")
    else:
        print("연결실패.....")


def btn_plus(data):
    global angle    
    if (angle+data) >= 180:
        angle = 180
    else:
       angle = angle + data
       
    duty = getDuty(angle)
    print(angle)
    pwm_servo.ChangeDutyCycle(duty)
    print("각도:",angle)
    
    
def btn_minus(data):
    global angle
    if (angle-data) <= 0:
        angle = 0
    else:
        angle = angle - data
    duty = getDuty(angle)
    print(angle)
    pwm_servo.ChangeDutyCycle(duty)
    print("각도:",angle)
            
            
def on_message(client, userdata, message):
    myval = message.payload.decode("utf-8").split(":")
    #print(message.topic+"-----"+myval)
    if myval[0] =="led_on":
        print("on")
        gpio.output(led_pin,gpio.HIGH)
    elif myval[0] == "led_off":
        print("off")
        gpio.output(led_pin,gpio.LOW)     
    elif myval[0] == "plus":
        print("플러스")
        btn_plus(int(myval[1]))
    elif myval[0] == "minus":
        print("마이너스")    
        btn_minus(int(myval[1]))
        
        
try:
    mqttClient = mqtt.Client()
    mqttClient.on_connect = connect_result
    mqttClient.on_message = on_message # 메시지가 broker에서 전달됐을때 콜백함수가 호출되도록 등록
    mqttClient.connect("172.30.1.57",1883,60)
    mqttClient.loop_forever() # 등록한 토픽의 메시지를 broker에서 전송받아야 하므로 대기
except KeyboardInterrupt:
    pass
finally:
    pwm_servo.ChangeDutyCycle(2.5)
    time.sleep(0.5)
    pwm_servo.stop()
    gpio.cleanup()

※ 한계 : 싱글쓰레드로는 지속적으로 PC에서 입력값을 보내는 것과 0, 90, 180도일 때 메시지를 받는 것을 동시에 수행하는 것이 불가능하다. 다음 수업에서 멀티쓰레드를 이용해 이를 구현할 것이다.

 

- 끝 -

 

728x90