2021정보영재>피지컬컴퓨팅

3주차

구현 목표: Python GUI 프로그램에서 아두이노 삼색신호등 제어하기

  • 원격으로 LED를 제어할 수 있다면, 전등(L), 에어컨(A), 출입문(D)도 같은 원리로 제어할 수 있다.

  • 삼색신호등의 세가지 LED를 각각 다음과 같이 모델링하여 제어한다.

    • 빨강LED - 전등(Lamp)

    • 노랑LED - 에어컨(Air conditioner)

    • 초록LED - 출입문(Door)

파이썬 제어 화면

파이썬 코드: 5_btnSerialControl-4.py

아두이노 회로 구성

펌웨어: serial_conrol_RGB.ino

아두이노 펌웨어 및 회로도: [실습6] 코드와 회로도 이용



  • 회로 구성

[아두이노]-[삼색신호등]- [기능 모델링]

13번 - 빨강LED - 전등(L)

12번 - 노랑LED - 에어컨(A)

11번 - 초록LED - 출입문(D)

GND - GND


  • 명령어(프로토콜)

  • L0: 전등 끄기, L1: 전등 켜기

  • A0: 에어컨 끄기, A1: 에어컨 켜기

  • D0: 출입문 닫기, D1: 출입문 열기

아두이노 소스코드 6_serial_conrol_RGB.ino

// 시리얼 통신으로 신호등 LED 제어하기

// [가정] 빨강led:전등(L), 노랑led:에어컨(A), 초록led:출입문(D)

// [명령어] 0: 끄기(닫기), 1: 켜기(열기)

// 예)L0(전등 끄기), A1(에어컨 켜기), D1(문 열기)


#define LED_R 13 // 빨강led, 전등(L)

#define LED_Y 12 // 노랑led, 에어컨(A)

#define LED_G 11 // 초록led, 출입문(D)


String inString = "";

char inChar;


void setup() {

Serial.begin(9600);

Serial.println("Arduino ready.");

pinMode(LED_R, OUTPUT);

pinMode(LED_Y, OUTPUT);

pinMode(LED_G, OUTPUT);

}


void loop() {

if (Serial.available()){

while(Serial.available()){

inChar = (char)Serial.read();

inString += inChar;

}


// 수신 문자열 파싱

String inNumber = inString.substring(1, inString.length()); // 제어값 x만 취함

int inValue = inNumber.toInt(); // 제어값 x를 정수로 변환


if(inString[0]=='L'){

digitalWrite(LED_R, inValue); // 전등 제어

Serial.println("Lamp "+(String)inValue);


}else if(inString[0]=='A'){

digitalWrite(LED_Y, inValue); // 에어컨 제어

Serial.println("Aircon "+(String)inValue);


}else if(inString[0]=='D'){

digitalWrite(LED_G, inValue); // 출입문 제어

Serial.println("Door "+(String)inValue);

}else

Serial.println("What?"); // 유효하지 않은 명령어

// 다음 문자열 수신 준비

inString = "";

}


delay(100);

}


파이썬 프로그래밍

파이썬 IDLE 설치 - Thonny

파이썬 직렬 통신 환경 설정

  • 윈도우 cmd 창에서 다음 명령을 실행

    • pip 업그레이드

python -m pip install --upgrade pip

  • pyserial 설치

pip install pyserial

파이썬 직렬 통신 테스트 0_serialTest.py

# 아두이노와 직렬 통신 연결 수립 및 제어

import serial #pyserial 라이브러리


# 아두이노와 직렬 통신 연결(속도 9600, 포트 COMx)

arduino = serial.Serial('COM8' , 9600)


# 아두이노의 시작 메시지 수신

if arduino.readable():

print(arduino.readline().decode())


while True:

cmd = input("제어명령(종료는 x): ")


if cmd == 'x' or cmd == 'X':

break

elif cmd == 'L0' or cmd == 'L1':

cmd = cmd.encode('utf-8')

arduino.write(cmd)

if arduino.readable():

print(arduino.readline().decode())



else:

print(" 알 수 없는 명령어입니다. \n [명령어 예시] L0, L1, A0, A1, D0, D1")


arduino.close() # 직렬 통신 종료



# 출처: https://haesolhanja.tistory.com/45 [KoverJK's Blog]

[프로그램 설명 및 실행]

  • 아두이노는 직렬 통신 연결이 수립되면 재부팅

  • 아두이노로 부터 직렬 통신 수립 메시지 수신하기

  • 아두이노에게 LED 제어 명령을 송신하여 동작 확인하기

[실습1] 파이썬 직렬 통신 1_serialControl.py

# 아두이노와 직렬 통신 연결 수립

import serial


cmd_type = ('L0', 'L1', 'A0', 'A1', 'D0', 'D1') # 명령어 종류


# 아두이노와 직렬통신 연결 수립(속도 9600, 포트 COMx)

arduino = serial.Serial('COM8' , 9600)


# 아두이노의 시작 메시지 수신

if arduino.readable():

print(arduino.readline().decode())


while True:

cmd = input("제어명령(종료는 x): ")


if cmd == 'x' or cmd == 'X':

break

if cmd in cmd_type:

cmd = cmd.encode('utf-8')

arduino.write(cmd)

if arduino.readable():

print(arduino.readline().decode())


else:

print(" 알 수 없는 명령어입니다. \n [명령어 예시] L0, L1, A0, A1, D0, D1")


arduino.close()


# 출처: https://haesolhanja.tistory.com/45 [KoverJK's Blog]

[실습1] 실행

  • 다음의 명령어를 전송하고, 삼색 신호등의 변화 및 아두이노에서 수신한 문자를 확인한다

> L0: 램프(빨강led) 켜기

> L1: 램프(빨강led) 끄기

> A0: 에어컨(노랑led) 켜기

> A1: 에어컨(노랑led) 끄기

> D0: 출입문(초록led) 켜기

> D1: 출입문(초록led) 끄기

> l2: 유효하지 않은 명령어


Python GUI 프로그래밍

[실습2] tkinter 기본 구조(빈 화면 만들기) 2_frame.py

# 빈화면 만들기

from tkinter import *


root = Tk()

# 더 알아보기

# root.title("스마트홈 제어")

# root.geometry("640x480") # 가로 * 세로

# root.geometry("640x480+300+100") # 가로 * 세로 + x좌표 + y좌표

# root.resizable(True, False) # x(너비), y(높이) 값 변경 불가 (창 크기 변경 불가)


root.mainloop()


[실습2] tkinter 기본 구조

from tkinter import *


root = Tk() # 최상위 윈도우 창 생성


root.mainloop() # 윈도우 창이 종료될 때까지 실행

[실습3] 버튼 추가 3_button-1.py

# 버튼 추가

from tkinter import *


root = Tk()


btnLampON = Button(root, text="전등 ON") # 버튼 생성

btnLampON.pack() # 버튼 넣기


root.mainloop()


[실습3] 버튼 추가

btnLampON = Button(root, text="전등 ON")# 버튼 생성

  • root: 버튼 위젯을 배치할 윈도우 창

  • text: 버튼 위젯에 표시할 문자열


btnLampON.pack() # 버튼 넣기

  • .pack(): 위젯을 윈도우에 배치


[실습4] 윈도우 제목,크기 설정 3_button-2.py

# 윈도우 제목/크기 추가

from tkinter import *


root = Tk()

root.title("스마트홈 제어") # 윈도우 제목 추가

root.geometry("300x200") # 윈도우 크기 설정(가로 * 세로)


btnLampON = Button(root, text="램프 ON")

btnLampON.pack()


root.mainloop()


[실습4] 윈도우 제목, 크기 설정

root.title("스마트홈 제어")

  • .title: 윈도우 창의 제목 문자열 설정


root.geometry("300x200")

  • .geometry: 윈도우 창 크기 설정(가로 x 세로 픽셀)

[실습5] 버튼 추가, 버튼 크기/색상 설정 3_button-3.py

# 버튼 추가, 버튼 크기,색상 설정

from tkinter import *


root = Tk()

root.title("스마트홈 제어")

root.geometry("300x200") # 가로 * 세로


btnLampON = Button(root, fg="red", width=10, height=3, text="전등 ON")

btnLampON.pack()


btnLampOFF = Button(root, fg="blue", width=10, height=3, text="전등 OFF")

btnLampOFF.pack()


root.mainloop()


[실습5] 버튼 추가, 버튼 크기/색상 설정

btnLampON = Button(root, fg="red", width=10, height=3, text="전등 ON")

  • fg: 버튼 문자 색상(fore ground color)

  • width, height: 버튼 폭/높이 크기(픽셀)

btnLampOFF = Button(root, fg="blue", width=10, height=3, text="전등 OFF")

  • '전등 끄기' 버튼 추가

[실습6] 버튼 클릭 함수 구현 4_btnControl-1.py

# 버튼 추가, 버튼 크기,색상 설정

from tkinter import *


root = Tk()

root.title("스마트홈 제어")

root.geometry("300x200") # 가로 * 세로


btnLampON = Button(root, fg="red", width=10, height=3, text="전등 ON")

btnLampON.pack()


btnLampOFF = Button(root, fg="blue", width=10, height=3, text="전등 OFF")

btnLampOFF.pack()


root.mainloop()


[실습6] 버튼을 클릭하면 실행창에 결과가 나타남

def lampONcmd():

print("전등을 켜시오 L1")

btnLampON = Button(root, fg="red", width=10,
height=
3, text="전등 ON", command=lampONcmd)

  • lampONcmd(): '전등 켜기' 실행 함수

  • command=: 버튼 클릭 시 실행할 함수

[실습7] 실행 결과를 문자표시 위젯에 표시 4_btnControl-2.py

# 문자표시 위젯 추가, 실행결과를 문자표시 위젯에 나타냄

from tkinter import *

import tkinter.font # 글자 폰트 모듈


root = Tk()

root.title("스마트홈 제어")

root.geometry("300x200") # 가로 * 세로

myFont = tkinter.font.Font(family="맑은 고딕", size=20) # 글자 폰트, 크기 설정


def lampONcmd():

print("전등을 켜시오 L1")

txtState.delete(1.0,"end")

txtState.insert(1.0,"전등을 켜시오 L1")


def lampOFFcmd():

print("전등을 끄시오 L0")

txtState.delete(1.0,"end")

txtState.insert(1.0,"전등을 끄시오 L0")


btnLampON = Button(root, fg="red", width=10, height=3, text="전등 ON", command=lampONcmd)

btnLampON.pack()


btnLampOFF = Button(root, fg="blue", width=10, height=3, text="전등 OFF", command=lampOFFcmd)

btnLampOFF.pack()


txtState=Text(root, width=20, height=2, font=myFont) # 문자표시 위젯 추가

txtState.pack(side="bottom") # 문자표시 위젯을 윈도우 맨아래에 배치


root.mainloop()



[실습7] 버튼을 클릭하면 문자표시 위젯에 결과가 나타남

def lampONcmd():

print("전등을 켜시오 L1")

txtState.delete(1.0,"end")

txtState.insert(1.0,"전등을 켜시오 L1")

  • txtState.delete: 현재 표시된 문자열을 지운다.

  • txtState.insert: 새로운 문자열을 넣는다.

txtState=Text(root, width=20, height=2, font=myFont)

  • Text(): 문자표시 위젯 추가

[실습8] 직렬 통신으로 아두이노 전등(L) 제어 5_btnSerialControl-1.py

# 직렬 통신으로 아두이노 전등(L) 제어

from tkinter import *

import tkinter.font

import serial


arduino = serial.Serial('COM8' , 9600)


if arduino.readable():

print(arduino.readline().decode())


root = Tk()

root.title("스마트홈 제어")

root.geometry("300x200") # 가로 * 세로

myFont = tkinter.font.Font(family="맑은 고딕", size=20)


def lampONcmd():

cmd = "L1"

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)


def lampOFFcmd():

cmd = "L0"

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)


btnLampON = Button(root, fg="red", width=10, height=3, text="전등 ON", command=lampONcmd)

btnLampON.pack()


btnLampOFF = Button(root, fg="blue", width=10, height=3, text="전등 OFF", command=lampOFFcmd)

btnLampOFF.pack()


txtState=Text(root, width=20, height=2, font=myFont)

txtState.pack(side="bottom")


root.mainloop()


arduino.close()




파이썬 제어 화면

① 전등 ON 버튼 클릭▶ L1 명령 →

수신 결과 표시◀ 결과 수신← ④

아두이노 회로 동작

② →명령 수신 ▶빨강 LED 켜짐

← Lamp 1 ◀실행 결과 송신 ③

[실습9] 위젯 grid 배치, 버전 표시 5_btnSerialControl-2.py

# 직렬 통신으로 아두이노 전등(L) 제어 - 화면배치 grid, ver표기

from tkinter import *

import tkinter.font

import serial


arduino = serial.Serial('COM3' , 9600)


if arduino.readable():

print(arduino.readline().decode())


root = Tk()

root.title("스마트홈 제어")

# root.geometry("300x200") # 가로 * 세로

myFont = tkinter.font.Font(family="Arial", size=12, weight="bold")


def lampONcmd():

cmd = "L1"

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)


def lampOFFcmd():

cmd = "L0"

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)


txtState = Text(root, width=20, height=2, font ="Consolas 16", relief="sunken") # 20글자, 2줄

txtState.insert(1.0, " Welcome! \n AIoT ver. 0.1")

txtState.grid(row=0, column=0, columnspan=2, padx=10, pady=10)


btnLampON = Button(root, fg="red", width=8, height=1, text="전등 ON", font=myFont, command=lampONcmd)

btnLampON.grid(row=1, column=0, padx=10, pady=10)


btnLampOFF = Button(root, fg="blue", width=8, height=1, text="전등 OFF", font=myFont, command=lampOFFcmd)

btnLampOFF.grid(row=1, column=1, padx=10, pady=10)


lblVersion = Label(root, text="AIoT ver. 0.1 by KSYUN, Copyleft 2021")

lblVersion.grid(row=2, column=0, columnspan=2, padx=5, pady=5)


root.mainloop()


arduino.close()




파이썬 제어 화면

위젯 배치 그리드 설정

[실습10] 람다 함수로 구현 5_btnSerialControl-3.py

# 직렬 통신으로 아두이노 전등(L) 제어 - lamda 함수로 표현

from tkinter import *

import tkinter.font

import serial


arduino = serial.Serial('COM3' , 9600)


if arduino.readable():

print(arduino.readline().decode())


root = Tk()

root.title("스마트홈 제어")

# root.geometry("300x200") # 가로 * 세로

myFont = tkinter.font.Font(family="Arial", size=12, weight="bold")


def cmdBtnClick(cmd):

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)


txtState = Text(root, width=20, height=2, font ="Consolas 16", relief="sunken") # 20글자, 2줄

txtState.insert(1.0, " Welcome! \n AIoT ver. 0.1")

txtState.grid(row=0, column=0, columnspan=2, padx=10, pady=10)


btnLampON = Button(root, fg="red", width=8, height=1, text="전등 ON", font=myFont, command=lambda:cmdBtnClick("L1"))

btnLampON.grid(row=1, column=0, padx=10, pady=10)


btnLampOFF = Button(root, fg="blue", width=8, height=1, text="전등 OFF", font=myFont, command=lambda:cmdBtnClick("L0"))

btnLampOFF.grid(row=1, column=1, padx=10, pady=10)


lblVersion = Label(root, text="AIoT ver. 0.1 by KSYUN, Copyleft 2021")

lblVersion.grid(row=2, column=0, columnspan=2, padx=5, pady=5)

root.mainloop()


arduino.close()


파이썬 제어 화면

[실습10] 유사한 명령을 람다 함수 인수로 전달

def cmdBtnClick(cmd):

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)

  • 명령어 인수 cmd 를 처리하는 함수

btnLampON = Button(root, fg="red", width=8, height=1, text="전등 ON", font=myFont, command=lambda:cmdBtnClick("L1"))

  • 명령어를 람다 함수의 인수로 전달

[완성] 직렬 통신으로 아두이노 전등, 에어컨, 출입문 제어 5_btnSerialControl-4.py

# 직렬 통신으로 아두이노 전등(L), 에어컨(A), 출입문(D) 제어

from tkinter import *

import tkinter.font

import serial


arduino = serial.Serial('COM3' , 9600)


if arduino.readable():

print(arduino.readline().decode())


root = Tk()

root.title("스마트홈 제어")

# root.geometry("300x200") # 가로 * 세로

myFont = tkinter.font.Font(family="Arial", size=12, weight="bold")


def cmdBtnClick(cmd):

arduino.write(cmd.encode('utf-8'))

if arduino.readable():

state = arduino.readline().decode()

txtState.delete(1.0,"end")

txtState.insert(1.0, "Tx>> "+ cmd + "\nRx<< " + state)


txtState = Text(root, width=20, height=2, font ="Consolas 16", relief="sunken") # 20글자, 2줄

txtState.insert(1.0, " Welcome! \n AIoT ver. 0.1")

txtState.grid(row=0, column=0, columnspan=2, padx=10, pady=10)


btnLampON = Button(root, bg="red", width=10, height=1, text="전등 ON", font=("Arial", 11, "bold"), command=lambda:cmdBtnClick("L1"))

btnLampON.grid(row=1, column=0, padx=5, pady=5)


btnLampOFF = Button(root, fg="blue", width=10, height=1, text="전등 OFF", font=("Arial", 11 ,"bold"), command=lambda:cmdBtnClick("L0"))

btnLampOFF.grid(row=1, column=1, padx=5, pady=5)


btnAirON = Button(root, bg="yellow", width=10, height=1, text="에어컨 ON", font=("Arial", 11, "bold"), command=lambda:cmdBtnClick("A1"))

btnAirON.grid(row=2, column=0, padx=5, pady=5)


btnAirOFF = Button(root, fg="blue", width=10, height=1, text="에어컨 OFF", font=("Arial", 11 ,"bold"), command=lambda:cmdBtnClick("A0"))

btnAirOFF.grid(row=2, column=1, padx=5, pady=5)


btnDoorON = Button(root, bg="green", width=10, height=1, text="출입문 Open", font=("Arial", 11, "bold"), command=lambda:cmdBtnClick("D1"))

btnDoorON.grid(row=3, column=0, padx=5, pady=5)


btnDoorOFF = Button(root, fg="blue", width=10, height=1, text="출입문 Close", font=("Arial", 11 ,"bold"), command=lambda:cmdBtnClick("D0"))

btnDoorOFF.grid(row=3, column=1, padx=5, pady=5)


lblVersion = Label(root, text="AIoT ver. 0.2 by KSYUN, Copyleft 2021")

lblVersion.grid(row=4, column=0, columnspan=2, padx=5, pady=5)

root.mainloop()


arduino.close()


작동영상.mp4

동영상이 재생되지 않을 때 여기 https://youtu.be/xpaSTGQuADw 에서 볼 수 있음.