알렉사로 컴퓨터를 원격으로 켜보자 !! (2)


안녕하세요 라이프온룸 호그르 입니다. 🙂

저번시간에 컴퓨터를 원격으로 켜는 기기를 만들어 봤죠 ? (링크참조)

오늘은 알렉사로 저번 시간에 만든 기기를 제어해 보겠습니다. !!! 준비물이 뭐가 필요한지 부터 보고 가죠 !

 

1. 준비물 

 

2. Fauxmo 코딩

새로운 파일을 하나 만들게요 !! threadFunction.py 라는 파일인데요 Bluetooth 연결, Data 전송, 연결 Check 에 관한 코드를 넣을 거에요 !! 그리고 여러가지 작업을 해야 하니 thread로 처리 해야 겠죠 ? 코드는 아래와 같습니다. 

# -*- coding: utf-8 -*-
#!/usr/bin/env python
import threading
import logging
import Queue
import bluetooth
import time
import sys

bt_q = Queue.Queue()
conn_check_q = Queue.Queue()

RECONNECTION_TIME = 30 #재연결 시간
BT_CONN_MSG_SEND_TIME = 240 #주기적으로 Bluetooth 살아 있는지 확인 하는 시간


HC_06_com_addr = "20:14:04:11:22:37"
HC_06_com_port = 1

root = logging.getLogger()
root.setLevel(logging.DEBUG)
logging.basicConfig(filename= 'echo_mqtt.log', level=logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
root.addHandler(handler)


TimerDic = {}
ThreadList = []
BTCycle = "BTCycle"
# thread
class MThread(threading.Thread):
    def __init__(self, ID, name, function, *args):
        threading.Thread.__init__(self)
        self.ID = ID
        self.name = name
        self.function = function
        self.input_tuple = tuple(value for _, value in enumerate(args))

        self.stop_flag = threading.Event()
        self.stop_flag.set()
        self.input_tuple = self.input_tuple + (self.stop_flag,)

    def run(self):
        logging.debug("starting " + self.name)
        # LedJarThreadLock.acquire()
        self.function(*self.input_tuple)
        # LedJarThreadLock.release()
        logging.debug("ending " + self.name)

# timer
class MTimer:
    kill_evt = 0
    def __init__(self,name,delay,count,fuction,*args):
        self.name = name
        #if name
        self.delay = delay
        self.count = count
        self.fuction = fuction
        self.args = tuple(value for _, value in enumerate(args))
        self.thread = threading.Timer(self.delay, self.handle_function)

    def handle_function(self):
        if self.count != -1:
            self.count -= 1

        self.fuction(*self.args)
        self.thread = threading.Timer(self.delay, self.handle_function)
        self.thread.start()

        if self.kill_evt == 1 or self.count == 0:
            logging.debug ('[%s]timer kill len  kill = %d, count = %d ' %  (self.name, self.kill_evt, self.count))
            self.thread.cancel()

    def start(self):
        logging.debug ('[%s]timer start !'%self.name)
        self.thread.start()

    def cancel(self):
        logging.debug ('[%s]timer stop !'%self.name)
        self.kill_evt = 1

        self.thread.cancel()

    def isAlive(self):
        val = self.thread.isAlive()
        logging.debug ('[%s]timer status %d !'% (self.name, val))
        return val


# bluetooth ------------------------------------
# bluetooth 연결 함수 데이터 Send thread 와 연결 확인 Timer 를 생성
def connectBluetooth(adress, port):
    try:
        sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
        sock.connect((adress, port))
        sock.settimeout(2)

        logging.debug("Bluetooth Connected !")
        # TX Thread 생성
        bt_tx = MThread(1, "SendBT", sendDataToBT, sock)
        bt_tx.start()
        ThreadList.append(bt_tx)


        sendFunction(sock, 'B_Start')

        # 블루투스 Check Timer 생성
        bt_cycle = MTimer(BTCycle, BT_CONN_MSG_SEND_TIME, -1, sendFunction, sock, 'B_Start')
        bt_cycle.start()
        TimerDic[BTCycle] = bt_cycle

        # Queue를 비워 줌
        while True:
            try:
                para = conn_check_q.get_nowait()
                logging.debug('Flush Q:', str(para))
            except Queue.Empty:
                break;

    except Exception, e:
        logging.debug("Bluetooth error Occured ! ! " + str(e))
        conn_check_q.put_nowait(1)

# bluetooth 데이터 Sends 함수
def sendFunction(bt_sock, data):
    try:
        strs = "msg send : " + str(data)
        logging.debug(strs)
        bt_sock.send(data)
    except:
        logging.debug("Bluetooth socket send error")
        conn_check_q.put_nowait(1)
        pass

# Callback 함수
def sendDataToBT(sock, t_event):
    while t_event.is_set():
        try:
            data = bt_q.get_nowait()
            sendFunction(sock, data)
        except Queue.Empty:
            pass
        except Exception, e:
            logging.debug(str(e))
        time.sleep(0.1)
    logging.debug('sendDataToBT Thread Kill !')
    sock.close()

# 재연결 함수
def recoveryProcess(adress, port, t_event):
    while t_event.is_set():
        try:
            para = conn_check_q.get_nowait()
            #thread_list_mutex.acquire()
            if para == 1:
                try:

                    #logging.debug('Thread list ' + str(len(ThreadList)))
                    while len(ThreadList) >0:

                        thread = ThreadList.pop()
                        logging.debug('Get Thread Name :' + thread.name + ' kill by Recovery Proc')
                        thread.stop_flag.clear()

                except:
                    logging.debug('Thread kill error')
                try:
                    #logging.debug('Timer Dic ' + str(len(TimerDic)))
                    for key, value in TimerDic.iteritems():
                        logging.debug('Timer Kill :' + 'kill by Recovery Proc')
                        value.kill_evt = 1

                    TimerDic.clear()

                except:
                    logging.debug('Timer kill error')
                time.sleep(RECONNECTION_TIME)

                connectBluetooth(adress, port)

        except Queue.Empty:
            pass
        except Exception, e:
            print 'RecoveryProcess error' + str(e)
        time.sleep(0.1)


if __name__ == "__main__":

    pass

 

좀 길죠 .. 설명은 주석을 참고 하시기 바랍니다. 그 다음엔 example-minimal.py 에서 위 코드를 Import 하여 에코닷과 연결하는 코드를 짜보도록 하죠 !! 아래와 같이 입력하면 됩니다. !

""" fauxmo_minimal.py - Fabricate.IO

    This is a demo python file showing what can be done with the debounce_handler.
    The handler prints True when you say "Alexa, device on" and False when you say
    "Alexa, device off".

    If you have two or more Echos, it only handles the one that hears you more clearly.
    You can have an Echo per room and not worry about your handlers triggering for
    those other rooms.

    The IP of the triggering Echo is also passed into the act() function, so you can
    do different things based on which Echo triggered the handler.
"""

import fauxmo
import logging
import time
import threadFunction as tf
from debounce_handler import debounce_handler

logging.basicConfig(level=logging.DEBUG)

# ------------ Bluetooth Address -----------
HC_06_com_addr = "20:14:04:11:22:37"
HC_06_com_port = 1

# ------------ Device Name -----------------
COMPUTER = 'Desktop'



class device_handler(debounce_handler):
    """Publishes the on/off state requested,
       and the IP address of the Echo making the request.
    """
    # Device
    TRIGGERS = {COMPUTER: 51999}

    def act(self, client_address, state, name):
        print "State", state, "on ", name, "from client @", client_address
        # "Desktop" 에 관한 명령이 Echo dot 으로 들어오면 처리해 주는 부분
        if name == COMPUTER:
            if state == True:
                tf.bt_q.put_nowait('True')
        return True

if __name__ == "__main__":
    # Startup the fauxmo server
    fauxmo.DEBUG = True
    p = fauxmo.poller()
    u = fauxmo.upnp_broadcast_responder()
    u.init_socket()
    p.add(u)

    tf.connectBluetooth(HC_06_com_addr, HC_06_com_port)
    RecoveryThread = tf.MThread(1, "RecoveryThread", tf.recoveryProcess, HC_06_com_addr, HC_06_com_port)
    RecoveryThread.start()
    print 'fauxmo start'
    # Register the device callback as a fauxmo handler
    d = device_handler()
    for trig, port in d.TRIGGERS.items():
        fauxmo.fauxmo(trig, u, p, None, port, d)

    # Loop and poll for incoming Echo requests
    logging.debug("Entering fauxmo polling loop")
    while True:
        try:
            # Allow time for a ctrl-c to stop the process
            p.poll(100)
            time.sleep(0.1)
        except Exception, e:
            logging.critical("Critical exception: " + str(e))
            RecoveryThread.stop_flag.clear()
            RecoveryThread.join()

main 함수에서 Bluetooth를 연결하는 함수를 불러주고 RecoveryThread를 만들어 줍니다. device_handler class 에서는 echo dot 에서 쓰일 Device 이름을 만들어 주고 해당 Device로 신호가 들어 왔을 때 어떤 처리를 할지 정해 줍니다. !

자 그러면 이제 테스트를 해보죠 !! 아래 명령어로 fauxmo 를 실행 시켜 줍니다. echowemoclient 폴더에 들어간 다음 가상환경을 Enable하고 코드를 실행시켜 줍니다. 아래처럼요 !

 

pi@raspberrypi:/home/home_iot/echowemoclient $ source ecvenv/bin/activate
(ecvenv) pi@raspberrypi:/home/home_iot/echowemoclient $ python example-minimal.py

블루투스 연결에 실패 하면 아래처럼 나올 거에요 !

성공하면 아래 처럼 나올 거구요 !

그러면 이 상태에서 알렉사에게 외쳐 줍니다. 

알렉사 파인드 커넥티드 디바이시스

넵 디바이스가 성공적으로 찾아지면 알렉사 얩에서 아래처럼 Desktop 인란 Device가 추가된 것을 볼 수 있습니다. 알렉사가 찾았다고 말도 해주고요 !

그럼 이 상태에서 외칩니다. 

 

알렉사 ! 턴 온 데스크탑

그럼 아두이노에 연결된 Relay가 딸깍 할거에요 그러면서 컴퓨터가 켜지는 거죠 !

3. 백그라운드 실행 

넵 여기까지 했으면 일단 기능적 할일은 끝입니다. !!!! Good 수고하셨습니다. ㅋㅋ

하지만 몇가지 일을 더 해줘야 수월하게 쓸 수 있는데요 우선 프로그램을 백그라운드로 실행 시켜야 합니다. 현제 터미널에서 Python 명령으로 프로그램을 실행 시켰는데요, 이렇게 되면 터미널을 종료하면 프로세스도 종료하게 됩니다. 

이렇게 되면 아니 되겠죠 !!

그래서 nohup 이라는 명령어를 씁니다. 우선 실행 시켜논 프로그램을 종료 시켜 볼게요 터미널에서 아래와 같이 입력합니다. 

  • Ctrl + z
  • pkill -9 python 

다음으로 start.sh 파일을 만듭니다. vim start.sh 

편집기가 열릴텐데요 거기에 아래 내용을 복붙 한 뒤 :wq 로 빠져 나옵니다. 

nohup python example-minimal.py &

start.sh 의 권한을 올리기 위해 chmod 777 start.sh 명령어를 입력해 줍니다. 그리고 start.sh를 실행 시킵니다.

(ecvenv) pi@raspberrypi:/home/home_iot/echowemoclient $ ./start.sh

자 그럼 블루투스 연결이 되었을 건데요 이때 터미널을 종료해도 블루투스 연결은 유지 됩니다. 즉 프로그램이 계속 켜져있게 된다는 얘기지요 

4. 부팅 시 자동 실행

이 부분은 선택사항이긴 한데요 ! 장기간 여행을 가서 두꺼비집을 내린다거나 갑자기 집이 정전 된다거나 하면 라즈베리파이가 꺼지겠죠! 

이때 라즈베리 파이를 다시 켜면 또 터미널에 들어가서 start.sh를 실행시켜 줘야 합니다. 핵귀찮 이겠죠 ㅜㅜ 

그래서 부팅시에 해당 프로그램을 자동으로 실행시키도록 해봅니다. 

rc.local 이라는 파일을 수정해 볼 건데요 ! rc.local을 열어보죠 !

sudo vim /etc/rc.local

눈치 채셨겠지만 exit0 바로 이전에 프로그램 실행 명령어를 입력해 놓시면 됩니다. 

nohup {파인썬 가상환경 절대경로}/python {파인썬 프로그램 절대 경로}/example-minimal.py &

이렇게 하시고 sudo reboot를 입력해 보세요 ! 라즈베리파이가 종료 되었다가 켜지면서 블루투스가 끊어졌다가 다시 연결될 것 입니다. 

5. Git 

넵 이것으로 기나긴 컴퓨터 자동켜기 포스팅이 완료 되었습니다. Arduino 그리고 라즈베리파이에 사용된 코드는 아래 Git 에 올려두었어요 ! 

https://github.com/hog225/echowemoclient

이 글로 핵 인싸처럼 컴퓨터를 켜보세요 ! ㅋㅋ

 

You may also like...

댓글 남기기

이메일은 공개되지 않습니다.