소프트웨어와 생활

공기를 보여 주는 빛 만들기

눈에 안 보이던 것을
색으로 옮겨 겁니다

학습 목표

  1. 만들 작품을 무엇을 받아 무엇으로 답할지로 미리 정리할 수 있다.
  2. 센서가 읽은 값을 색이라는 다른 형태로 옮기는 프로그램을 쓸 수 있다.
  3. 잠깐 넘는 값과 이어지는 값을 구별해 알림이 시끄러워지지 않게 할 수 있다.

무엇을 만드나

교실의 공기는 눈에 보이지 않습니다. 답답해진 것을 알아채는 사람도 있고 끝까지 모르는 사람도 있습니다. 이번에 만들 것은 그 보이지 않는 것을 색으로 옮겨 벽에 거는 작품입니다.

「공기를 보여 주는 빛」

교실 뒤 벽에 가로로 긴 빛 띠를 겁니다. 습도가 알맞으면 은은한 초록, 높아지면 노랑, 더 높아지면 붉은빛, 너무 메마르면 푸른빛으로 물듭니다. 붉은 상태가 한동안 이어지면 짧게 한 번만 소리로 알립니다.

먼저 표로 정리한다

만들 작품을 다섯 칸으로 정리한 표
하고 싶은 일 보이지 않는 교실 공기를 누구나 볼 수 있게 만들고 싶다.
무엇을 읽나 「답답함」은 센서로 못 읽는다. 대신 습도를 읽는다. 사람이 오래 머문 방은 대체로 습도가 오르기 때문이다. → 온습도 센서
어떻게 판단하나 습도 값을 네 구간으로 나누어 색을 정한다. 높은 구간이 3분 넘게 이어질 때만 소리를 낸다.
무엇으로 답하나 빛 띠 전체의 색으로 답한다. 소리는 딱 한 번만 쓴다. 교실이기 때문이다.
틀릴 수 있는 자리 센서 바로 옆만 읽는다. 창가에 두면 바깥 날씨에 끌려가고, 난방기 옆에 두면 방과 다른 값이 나온다.

습도는 답답함이 아니다

「무엇을 읽나」 칸을 보세요. 우리는 답답함을 측정하는 것이 아니라, 답답함과 함께 움직이는 다른 값을 측정하고 있습니다. 그래서 이 작품은 「공기가 나쁘다」고 말하지 않고 「습도가 이만큼이다」를 보여 줄 뿐입니다.

답답함과 더 가깝게 움직이는 값으로는 이산화 탄소 농도가 있고 그것을 읽는 센서도 있지만, 여기서는 온습도 센서 하나로 만듭니다. 어느 센서를 쓰든 이 구별을 흐리면 안 됩니다. 이 작품의 판단은 색을 고르는 데까지입니다. 창문을 열어야 할지 해석하고 결정하는 것은 여전히 사람의 몫입니다.

준비물과 연결

준비물과 연결할 핀
부품 신호선을 꽂을 핀 비고
빛 띠 GP0 코드의 DATA_PIN = 0과 맞춘다
온습도 센서 GP22 사람 손이 닿지 않는 자리에 붙인다
버저 GP15 소리가 작게 나도록 안쪽으로 향하게 둔다

세 부품 모두 전원선은 부품에 맞는 전압의 핀(3V3 또는 VBUS)에, 접지선은 GND 핀에 잇습니다. 접지선을 빠뜨리지 않았는지 다시 한번 보고 시작하세요.

스스로 확인

센서를 어디에 붙일지 교실을 둘러보고 한 자리를 정해 보세요. 그 자리를 고른 까닭과, 그 자리 때문에 생길 수 있는 문제를 하나씩 적습니다.

값 읽어 보기

작품을 한 번에 완성하려 하면 어디가 잘못됐는지 알 수 없습니다. 빛부터 붙이지 말고 센서가 제대로 읽는지부터 봅니다. 화면에 숫자를 찍어 보는 것이 가장 빠릅니다.

# 온습도 센서가 읽은 값을 화면에 찍어 본다.
# 센서의 신호선은 GP22번 핀에 꽂아 두었다고 본다.

from machine import Pin
import dht
import time

SENSOR_PIN = 22

sensor = dht.DHT11(Pin(SENSOR_PIN))

while True:
    sensor.measure()                 # 센서에게 지금 값을 측정하라고 시킨다
    temperature = sensor.temperature()
    humidity = sensor.humidity()
    print("온도", temperature, "도 / 습도", humidity, "%")

    time.sleep(3)   # 이 센서는 너무 자주 읽으면 값을 주지 않는다
  • sensor.measure()는 「지금 값을 측정하라」는 명령입니다. 반복 안에 이 줄이 없으면 humidity()는 지난번에 측정한 값을 그대로 돌려줍니다.
  • time.sleep(3)이 있는 까닭 — 이 센서는 너무 자주 물으면 값을 주지 않습니다. 1초 안에 두 번 부르면 오류가 날 수 있습니다.
  • print()로 찍은 것은 편집기 화면에 나옵니다. USB가 이어져 있을 때만 보입니다.

해 보기 — 숫자를 믿을 수 있는지 확인

  1. 가만히 두고 1분 동안 찍히는 값을 봅니다. 값이 얼마나 흔들리나요?
  2. 센서에 입김을 가까이 불어 봅니다. 습도가 오르는 데 몇 초쯤 걸리나요?
  3. 입김을 멈추고 원래 값으로 돌아오는 데 얼마나 걸리는지 봅니다.

돌아오는 데 걸리는 시간이 특히 중요합니다. 돌아오는 데 오래 걸린다면, 창문을 열어도 빛이 한참 뒤에야 초록으로 바뀔 것입니다. 그런 성질을 알고 만들어야 「고장 났나?」 하는 오해가 생기지 않습니다.

값이 안 나올 때 살펴볼 차례

  1. 접지선이 꽂혀 있는가
  2. 신호선이 GP22에 꽂혀 있는가. 코드의 숫자와 같은가
  3. 전원선이 부품에 맞는 전압의 핀에서 나왔는가
  4. 읽는 간격이 너무 짧지 않은가

이 차례로 보는 까닭은 위로 갈수록 확인하기 쉬운 것이기 때문입니다. 어려운 원인부터 의심하면 시간이 오래 걸립니다.

스스로 확인

읽는 간격을 1분으로 늘리면 무엇이 좋아지고 무엇이 나빠질까요? 「3분 넘게 이어질 때만 울린다」를 지키려면 알림 쪽에서 무엇을 함께 고쳐야 할지도 말해 보세요.

값을 색으로

이제 읽은 숫자를 색으로 옮깁니다. 이 옮기는 규칙이 작품의 중심입니다. 어디에서 색이 바뀌게 할지를 정하는 것이 작가가 하는 일입니다.

습도 값을 네 구간으로 나눈다 푸른빛 초록빛 노란빛 붉은빛 40 60 70 메마름 알맞음 슬슬 답답함 오래 두면 답답함

구간을 나누는 숫자 40 · 60 · 70은 하늘에서 떨어진 값이 아닙니다. 실내 습도가 대체로 이 언저리에서 오르내린다는 것을 알고 우리가 정한 값입니다. 교실을 며칠 지켜보고 바꾸어도 됩니다.

색을 고르는 부분

def color_for(humidity):
    """습도 값 하나를 색 하나로 옮긴다."""
    if humidity < 40:
        return (0, 10, 60)      # 메마름 - 푸른빛
    if humidity < 60:
        return (0, 40, 20)      # 알맞음 - 초록빛
    if humidity < HIGH_LIMIT:
        return (50, 35, 0)      # 슬슬 답답함 - 노란빛
    return (60, 0, 0)           # 높음 - 붉은빛
  • 낮은 쪽부터 차례로 물었습니다. if humidity < 40이 이미 걸러졌으므로, 다음 줄에서는 40 이상인 것만 남습니다. 그래서 40 <= humidity < 60이라고 길게 쓰지 않아도 됩니다.
  • 마지막은 if 없이 그냥 돌려줍니다. 여기까지 왔다는 것이 곧 70 이상이라는 뜻이기 때문입니다.
  • 이 함수는 빛을 켜지 않습니다. 색만 정해서 돌려줍니다. 정하는 일과 켜는 일을 나누어 두면 색만 따로 시험해 볼 수 있습니다.

구간으로 나누면 생기는 일

습도가 59와 60 사이를 오갈 때 색이 초록과 노랑을 왔다 갔다 합니다. 실제 공기는 거의 그대로인데 빛만 깜빡이는 것처럼 보입니다. 구간이 바뀌는 값을 경계라고 합니다. 구간을 쓰는 한 경계는 반드시 있고, 값이 경계 근처를 오가면 이런 일이 생깁니다.

줄이는 방법이 있습니다. 올라갈 때와 내려올 때의 기준을 다르게 두는 것입니다. 60에서 노랑이 되고 57에서야 초록으로 돌아오게 하면 59와 60 사이를 오가는 정도로는 왔다 갔다 하지 않습니다. 다만 규칙이 하나 더 늘어납니다.

스스로 확인

구간을 넷이 아니라 스무 개로 잘게 나누면 어떻게 될까요? 좋아지는 점과 나빠지는 점을 하나씩 들어 보세요.

알림 조정

이제 소리를 붙입니다. 소리는 다루기 까다로운 답입니다. 잘못 만들면 수업을 방해하고, 그러면 작품이 통째로 치워집니다. 지킬 것은 둘입니다. 잠깐 넘는 값에는 울리지 않기, 그리고 한 번만 울리기.

전체 프로그램

# 교실의 습도를 읽어 빛 띠의 색으로 보여 준다.
# 높은 상태가 한동안 이어지면 버저로 한 번만 짧게 알린다.
#
# 빛 띠의 신호선  -> GP0
# 온습도 센서     -> GP22
# 버저            -> GP15

from machine import Pin, PWM
import neopixel
import dht
import time

DATA_PIN = 0
SENSOR_PIN = 22
BUZZER_PIN = 15

COUNT = 8            # 띠에 달린 알의 개수
READ_EVERY = 5       # 몇 초마다 읽을지
HIGH_LIMIT = 70      # 이 값 이상이면 높다고 본다
HOLD_COUNT = 36      # 5초 x 36 = 180초, 곧 3분

strip = neopixel.NeoPixel(Pin(DATA_PIN), COUNT)
sensor = dht.DHT11(Pin(SENSOR_PIN))
buzzer = PWM(Pin(BUZZER_PIN))


def color_for(humidity):
    """습도 값 하나를 색 하나로 옮긴다."""
    if humidity < 40:
        return (0, 10, 60)      # 메마름 - 푸른빛
    if humidity < 60:
        return (0, 40, 20)      # 알맞음 - 초록빛
    if humidity < HIGH_LIMIT:
        return (50, 35, 0)      # 슬슬 답답함 - 노란빛
    return (60, 0, 0)           # 높음 - 붉은빛


def paint(color):
    """띠 전체를 한 가지 색으로 물들인다."""
    for i in range(COUNT):
        strip[i] = color
    strip.write()


def beep():
    """짧게 한 번 소리를 낸다."""
    buzzer.freq(880)
    buzzer.duty_u16(2000)
    time.sleep(0.2)
    buzzer.duty_u16(0)


high_streak = 0     # 높은 값이 몇 번이나 이어졌는지 센다

while True:
    sensor.measure()
    humidity = sensor.humidity()
    paint(color_for(humidity))

    if humidity >= HIGH_LIMIT:
        high_streak = high_streak + 1
    else:
        high_streak = 0

    if high_streak == HOLD_COUNT:   # 딱 맞을 때 한 번만 울린다
        beep()

    time.sleep(READ_EVERY)

가운데 high_streak라는 이름을 눈여겨보세요. 높은 값이 몇 번이나 이어졌는지를 세는 숫자입니다.

high_streak가 하는 일
코드 하는 일
high_streak = high_streak + 1 이번에도 높으면 세던 수를 하나 올린다.
high_streak = 0 한 번이라도 낮아지면 처음부터 다시 센다. 그래서 「이어진」 횟수가 된다.
if high_streak == HOLD_COUNT: 세던 수가 딱 36이 되는 그 순간에만 울린다. 37, 38로 넘어가면 다시 울리지 않는다.

>= 로 바꾸면 어떻게 될까

== 대신 >=를 쓰면 36에 이른 뒤로는 5초마다 계속 울립니다. 습도가 내려갈 때까지 몇십 번이 울릴 수도 있습니다. 한 글자 차이지만 교실에서 쫓겨나느냐 마느냐가 결정됩니다.

이런 부분은 시험하기가 번거롭습니다. 3분을 기다려야 하기 때문입니다. 그래서 시험할 때는 HOLD_COUNT를 잠깐 3으로 줄여 두고 확인한 뒤, 다시 되돌려 놓습니다.


해 보기 — 내 작품으로 고치기

  1. 색을 내 것으로 바꿉니다. 네 구간에 어떤 색을 쓸지는 여러분이 정합니다. 「답답함」을 붉은색으로 나타낼 이유는 사실 없습니다.
  2. 구간의 경계를 우리 교실에 맞게 고칩니다. 며칠 동안 값을 지켜본 뒤 정하면 더 좋습니다.
  3. 띠 전체를 한 색으로 칠하는 대신 값에 따라 켜지는 알의 개수가 달라지게 바꿔 보세요. paint()를 고치면 됩니다.
  4. 소리를 아예 빼고 다른 방법으로 알리는 것도 좋습니다. 예를 들어 붉은빛일 때만 아주 천천히 밝기가 오르내리게 할 수 있습니다.

다 고쳤으면 보드에 main.py로 저장해 전원만으로 돌게 하고, 작품 옆에 설명 카드를 붙입니다. 카드에는 무엇을 읽는지, 무엇은 읽지 않는지를 적습니다. 보는 사람이 안심할 수 있어야 합니다.

스스로 확인

설명 카드에 「무엇은 읽지 않는지」를 적으라고 했습니다. 이 작품이 읽지 않는 것에는 무엇이 있을까요? 세 가지만 적어 보세요.

핵심 정리

무엇을 만드나

  • 보이지 않는 교실 공기를 색으로 옮겨 벽에 거는 작품이다.
  • 답답함은 못 읽으므로 습도를 대신 읽는다. 둘은 같은 것이 아니다.
  • 그래서 이 작품의 판단은 색을 고르는 데까지고, 보여 줄 뿐이다. 해석과 결정은 사람의 몫이다.

값 읽어 보기

  • 한 번에 완성하지 말고 센서가 제대로 읽는지부터 확인한다.
  • 반복 안에서 measure()를 부르지 않으면 지난번 값이 그대로 나온다.
  • 값이 안 나오면 접지선 → 핀 번호 → 전압 → 읽는 간격 차례로 본다.

값을 색으로

  • 옮기는 규칙이 작품의 중심이다. 경계를 어디에 둘지가 작가의 몫이다.
  • 색을 정하는 일과 켜는 일을 나누면 따로 시험할 수 있다.
  • 값이 경계 근처를 오가면 색이 왔다 갔다 한다. 올라갈 때와 내려올 때의 기준을 다르게 두면 줄어든다.

알림 조정

  • 소리는 다루기 까다로운 답이다. 잘못 만들면 작품이 치워진다.
  • 이어진 횟수를 세면 잠깐 넘는 값을 걸러 낼 수 있다.
  • ==과 >= 한 글자 차이로 한 번 울리느냐 계속 울리느냐가 결정된다.

확인 퀴즈

배운 내용을 확인해 봅시다.

Q1

이 작품이 실제로 읽는 것은 무엇인가요?

Q2

반복 안에서 sensor.measure()를 다시 부르지 않으면 어떻게 되나요?

Q3

high_streak = 0으로 되돌리는 줄이 하는 일은?

Q4

습도가 59와 60을 오갈 때 빛이 깜빡이는 것처럼 보입니다. 줄이는 방법은?