코로나 집콕 프로젝트 #1

의도

2020년은 코로나의 해였다. 년초에 시작한 코로나는 년말을 지나도록 잡히지 않았고 나는 하반기 내내 외부 프로젝트를 줄이고 집에 박혀서 더 많은 삽질을 했다. 이유없는 삽질에 결론을 만들기는 어렵긴 하지만 무언가 결과가 있어야겠다고 생각한게 12월이니… 오래도 결렸다.

하반기에 주로 많이한 일들이 IoT 디바이스를 가지고 놀아본거였고 거기에 Flutter를 이용해서 앱을 두어개 만들어 스토어에 업로드 해본 경험으로 IoT Network을 만들 수 있지 않을까? 하는 생각을 했고 순수하게 우리집안에 온습도 정도를 측정해보자는 생각으로 12월초에 프로젝트를 시도 했다.

12월 16일

페이스북에 뭔가 하고 있다고 공유를 시작했는데… 대략 아래와 같은 기술 스택으로 프로토 타입을 만들어 봤다. 그림에 보이는 보드는 5~6달러 정도의 ESP32 호환 보드로 여기에 MicroPython을 올려서 DHT11 온습도 센서를 연결하고 받은 데이터를 Wifi – MQTT 프로토콜을 이용해서 AWS EC2 서버에 Python & FASTApi 기반으로 개발된 서버에 전송한다.

서버에서 MQTT로 수신한 데이터는 MariaDB에 저장하고 FASTApi는 모바일앱에서 데이터를 요청하면 REST 기반으로 데이터를 보내준다.

앱은 Flutter & Dart 로 개발되었고 iOS & Android를 지원한다. 서버에 측정 데이터를 요청하고 받은 측정 데이터를 보여준다. 대략의 기술 스택은 아래와 같다.

ESP32, Raspberry PI + DHT11 + MicroPython + MQTT + AWS EC2 + MariaDB + Flutter

https://www.facebook.com/rtlink.park/posts/4162860857063253

위에 보이는 라즈베리파이 Zero에도 ESP32와 똑같은 기능의 Python 코드를 별도로 만들어 서버로 전송하고 앱에서 데이터를 조회 할 수 있다. 그렇게 3개의 온습도 측정기를 거실과 베란다에 설치하고 측정을 시작했다.

저렴한 ESP32보드(5달러짜리임..)를 사용한 탓인지 중국산 건전지 탓인지 아니면 둘다의 문제인지 3일이 못가서 건전지가 다 소모 되었고 결국 5V 아답터로 교체했다. ESP32는 하루 한번 정도 다운이 되었는데 코드 버그인지 보드 문제인지 알수가 없었다 – 나중에 알게 되었지만 코드에도 문제가 있었다. ^^;

12월 21일

https://www.facebook.com/rtlink.park/posts/4176739459008726

원래 온습도를 측정하기 시작한 이유는 코로나 시즌이니 가족들의 건강을 위하여 실내환경을 좀더 쾌적하게 유지해 보려는 의도였는데 데이터를 앱으로만 보고 있으니 접근성이 좋지 않았다. 그리고 앱을 계속 디버깅하고 있으니 가족들에게 앱을 설치해주는 일도 쉽지 않았다.

그래서 온습도 전용 디스플레이를 만들어야겠다고 생각하고 아래의 모니터링 장비를 만들었다. 이 모니터는 라즈베이파이4와 전용 디스플레이로 구성되어 있으며 라즈비안OS가 설치되어 있는데 개발은 Vue.js + Nuxt.js로 개발 하였고 웹브라우저를 Full Screen으로 오픈하는 방식으로 화면을 채우고 주기적으로 서버에 REST를 통해 데이터를 요청하고 디스플레이한다.

앱에는 설정된 온습도 범위에 따라 데이터 컬러가 변하는 정도의 알람(?)과 그래프가 추가 되어서 시간별 데이터를 조회 할 수 있게 되었다.

12월 28일

크리스마스에도 달렸다. 코딩말고 뭘 더할게 있겠는가? ㅋ

https://www.facebook.com/rtlink.park/posts/4194455677237104

알리익스프레스에서 거의 1달만에 도착한 토양수분측정기를 동백나무 화분에 설치했다. 이것 역시 ESP32기반의 모듈이라 온습도 측정을 위해 만든 모듈에 토양수분 측정 기능만 추가 했는데 토양 수분 모듈은 기기별로 데이터의 캘리브레이션이 필요한데 일단 이건 나중에 하기로 하고 일반적인 수식을 적용 했더니 데이터가 정확히 맞지는 않는듯 했지만 시간이 지날수록 수분이 줄어들고 있다는것을 확인 할 수 있었다.

앱은 한눈에 모든 기기의 현재 데이터를 확인 할 수 있도록 바꾸었고 불루투스를 이용해서 디바이스의 초기 데이터(디바이스 코드, Wifi SSID)를 셋팅 하기 위하여 Flutter에서 블루투스 전송 작업을 시작했고, 디바이스를 더 붙여 보려는 욕심에 인벤토리를 뒤져서 먼지측정 센서를 찾아냈다. 하지만…

12월 31일

올해 마지막날이라고 해서 들뜬 분위기로 하루를 보낼수는 없다!

알리에서 주문한 새 ESP32 디바이스가 6개 그리고 와이파이 안테나가 한뭉치 왔다. 안테나를 붙이니 보드에 붙어있던 안테나에 비해 도달거리가 늘어났고 그래서 세탁실에도 온습도 측정기를 하나 더 달았다. 그리고 작업중이던 미세먼지 측정기도 연결하기 위해 배선 작업을 했지만 측정기에 문제가 있었는지 아니면 배선을 잘못했는지 아니면 코딩을 잘못했는지… (원인이 너무 많아 디버깅불가) 하여간 데이터가 들어 오지 않았다.

미세먼지 측정기는 가진게 하나밖에 없어서 두번째 시도를 못하고 그냥 두고 있다.

2021년 1월 3일

https://www.facebook.com/rtlink.park/posts/4210535365629135

홈 IoT의 꽃은 리모트 컨트롤이다. 앱에 버튼을 눌러 전등을 끄고 켜거나.. 끄고 켜거나.. 끄고 켜거나… 그거말고 뭘하지?… 하여간 기왕에 하는거 이걸 안하면 안될것 같아서 리모트 컨트롤 기능을 추가했다.

서버에서로 MQTT를 이벤트 방식으로 수신하기 때문에 당연히 같은 방법으로 ESP32에서도 수신 할 수 있을거라고 생각했지만 ESP32에서는 이벤트 방식으로 되지 않았다. 쓰레드 루프를 만들고 계속 확인을 하면서 리모트 컨트롤 데이터를 수신해야 했다.

앱에서는 리모트 컨트롤 인터페이스를 기존의 측정기 화면과 동일한 화면을 쓰게 만들긴 했는데… 나중에 별도의 화면으로 만들어야 될것 같다.

2021년 1월 8일

https://www.facebook.com/rtlink.park/posts/4224526074230064

하다보면 욕심이 생긴다. 데이터만 볼게 아니고 화면도 볼 수 있을까? 인벤토리를 뒤져보니 ESP32-Cam이 나왔다. 이건 언제 삿지? 하여간 붙여야지…

그래서 온습도를 측정중인 동백꽃을 동영상으로 볼 수 있도록 기능을 추가 했는데 MicroPython으로 ESP32-CAM을 연동하는 셈플이 많지않아서 C코드 기반의 MJPEG 스트리밍을 송신하고 앱에서는 웹뷰로 스트리밍을 수신하도록 구성했다. 로컬 Wifi IP로 라우팅되고 있어서 외부에 나가서는 볼 수가 없어서 일단은 공유기에서 포트포워딩을 하도록 만들었다.

아래 링크를 보면 ngrok를 쓸수 있다는 내용이 있는데… 나중에 해봐야겠다.

삽질은 2021년에도 계속 된다…

Jetson Nano, PCA9685, L298N, DC-Motor 4륜구동 자동차

Jetson Nano를 얹어서 완성된 자동차

Jetson Nano를 충동적으로 구입하고 (http://practical.kr/?p=125) 테스트를 해본 후 뭘 만들까 고민을 하다가 자동차를 만들어 볼까? 생각을 했다. 전년도에 라즈베리파이를 이용해서 자동차를 만들려고 구매해 두었던 4륜 자동차 바디가 그대로 있어서 여기에 젯슨나노를 얹어 보기로 했다.

실패한이유

근데 생각해 보니 그때 라즈베리 기반의 자동차를 만들다 그만둔 이유가 라즈베리파이의 Digital I/O 의 숫자가 부족하다는걸 알고 나서였다. 바퀴하나를 구동하려면 모터드라이브 컨트롤러를 써야하는데 주로 L298N을 많이 사용한다. 대략 아래와 같은 연결로 모터 두개를 움직일 수 있다.

전원부를 제외하고 모터 4개를 구동하려면 12개의 DIO를 사용해야 한다. 라즈베리에서 IO를 모두 끌어쓰면 대충 맞긴한데 이러면 다른 작업을 전혀 할 수가 없다. 그래서 주로 2륜 구동으로 자동차를 만드는게 아닌가 하는 생각을 하고 바퀴를 새로 사기가 싫어서 그냥 그만 두고 말았다.

PCA9685

이렇게 생겼다

그래도 젯슨은 뭔가 좀 다르겠지 싶었지만 라즈베리와 젯슨나노의 IO Pin 구조는 거의(?) 동일하다. 그런데 스터디중 Nvidia – Jetbot 에서는 PCA9685 라는 컨트롤러를 쓰고 있었다. 젯슨과 I2C로 통신을 하고 16개의 dio 채널을 가진 PWM 컨트롤러 였다. 대략 아래의 그림과 같이 문어발(?)을 만들 수 있다.

https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf

이걸 활용하면 젯슨나노에서는 4핀의 I2C 입출력 만으로 4개의 모터를 구동할 수 있고 나머지핀들은 다른용도로 활용할 수도 있다. 택배비 포함해서 2개에 만원정도니까 비싸지도 않다.

검색을 해보면 주로 서보모터를 운용할때 사용하는데 DC모터를 제대로 컨트롤 하려면 아래 그림과 같이 L298N과 함께 연결해줘야 한다.

자동차의 하체

하체의 중간에 PCA9685를 놓고 앞뒤로 L298N 모터 컨트롤러를 연결했다.모터 구동을 위한 별도의 전원은 9V 건전지를 사용했는데 조립중 테스트에서는 별 문제 없이 바퀴가 잘 돌았는데 다 조립하고 중량이 무거워지니 전후진이 안되었다. 그래서 9V 건전지 두개를 병렬로 연결했다. 움직이긴 하는데 힘이 없었다. 역시 전기 자동차는 배터리가 중요하다. 한개 더 달던지 해야 한다.

하체 조립중
하체 조립완료

차체조립

조립후

짧은 목표가 조립 & 이동 이었기 때문에 가지고 있던 이런저런 부품들을 모아 모아 모니터, 젯슨나노 & 모니터 전원을 위한 충전 배터리 향후 사용할 카메라까지 조립을 했다.

소프트웨어

검색을 해보니 PCA9685를 활용하는 대부분의 셈플들이 서보모터 구동용이라 찾기가 쉽지는 않았는데 다행히 원래 보드 제작사에서 만든 파이썬용 라이브러리가 있었다. 링크참조

https://github.com/adafruit/Adafruit_CircuitPython_Motor

https://cdn-learn.adafruit.com/downloads/pdf/adafruit-16-channel-servo-driver-with-raspberry-pi.pdf

위 라이브러리를 기반으로 아래와 같은 코드를 만들어 자동차를 움직여 봤다. 잘 움직인다.

import time
import busio

from board import SCL, SDA
from adafruit_pca9685 import PCA9685
from adafruit_motor import motor

i2c = busio.I2C(SCL, SDA)

pca = PCA9685(i2c, address=0x40)
pca.frequency = 100

pca.channels[0].duty_cycle = 0xFFFF
pca.channels[5].duty_cycle = 0xFFFF
pca.channels[6].duty_cycle = 0xFFFF
pca.channels[11].duty_cycle = 0xFFFF

motor1 = motor.DCMotor(pca.channels[1], pca.channels[2])
motor2 = motor.DCMotor(pca.channels[3], pca.channels[4])
motor3 = motor.DCMotor(pca.channels[8], pca.channels[7])
motor4 = motor.DCMotor(pca.channels[10], pca.channels[9])

print("Forwards slow")
motor1.throttle = 0.5
motor2.throttle = 0.5
motor3.throttle = 0.5
motor4.throttle = 0.5
time.sleep(3)

print("Forwards")
motor1.throttle = 1
motor2.throttle = 1
motor3.throttle = 1
motor4.throttle = 1
time.sleep(3)

print("Backwards")
motor1.throttle = -1
motor2.throttle = -1
motor3.throttle = -1
motor4.throttle = -1
time.sleep(3)

print("Backwards slow")
motor1.throttle = -0.5
motor2.throttle = -0.5
motor3.throttle = -0.5
motor4.throttle = -0.5
time.sleep(3)

print("Stop")
motor1.throttle = 0
motor2.throttle = 0
motor3.throttle = 0
motor4.throttle = 0
time.sleep(3)

print("Spin freely")
motor1.throttle = None
motor2.throttle = None
motor3.throttle = None
motor4.throttle = None

pca.deinit()

위 소스와 키입력으로 전, 후, 좌, 우로 이동하는 소스를 아래 링크에 공유 했다.

https://github.com/bipark/jetson-nano-car

실제 젯슨 나노의 GPU를 기반으로 자동차와 함께 할 수 있는 일들이 많을것 같은데 그건 다음에 해보기로 하고 오늘은 여기까지…

ESP32, MicroPython, MQTT

아래와 같은 삽질을 했다. 삽질은 기록이라고… 대충 정리함

ESP32에서 MicroPython을 언젠가 한번 사용해봐야지 하고 있었는데 기왕 하는 김에 MQTT까지 연결해봤다.

  1. ESP32 에 MicroPython을 설치한다.
  2. ESP32에서 MicroPython으로 MQTT Client를 만든다
  3. 맥북에 MQTT 서버를 설치한다
  4. 맥북에서 Python을 이용해서 MQTT Client를 만든다.
  5. ESP32 – 맥북사이의 MQTT Client와 데이터를 주고 받기 테스트

ESP32에 MicroPython 설치하기

ESP32에 MicroPython을 설치하려면 firmware를 다운 받아서 설치해야 한다. 다운로드는 아래 링크에서 받을 수 있다. 가지고 있는 하드웨어에 맞게 버전을 선택해서 다운 받으면 된다.

https://micropython.org/download/esp32/

다운받은 파일을 설치하기 위해서는 esptool을 사용할 수 있다.

$ pip install esptool

https://github.com/espressif/esptool

디바이스가 연결된 포트를 확인하고 (ex: /dev/tty.SLAB_USBtoUART) 아래와 같이 firmware를 설치한다.

기존데이터를 지우고
$ esptool.py --port /dev/tty.SLAB_USBtoUART erase_flash 

설치한다
$ esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 esp32-idf3-20200902-v1.13.bin

ESP32에 MQTT Client 만들기

개발은 VSCode를 이용해서 Pymakr 라는 Extension을 설치하고 코드를 쓰고 업로드와 실행 테스트를 할 수 있다. 설치하면 VSCode 아래 메뉴바에 Run, Upload등의 메뉴를 확인할 수 있다.

Pymakr은 아래 링크 참조
https://marketplace.visualstudio.com/items?itemName=pycom.Pymakr

아래 코드는 https://randomnerdtutorials.com/micropython-mqtt-esp32-esp8266/ 에서 참고 했다. boot.py는 ESP32 가 시작할때 Wifi에 연결하고 main.py에서 MQTT Client를 생성해서 데이터를 수신한다.

boot.py

import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
esp.osdebug(None)
import gc
gc.collect()

ssid = 'SSID'
password = 'PASSWORD'
mqtt_server = '192.168.0.7'
client_id = ubinascii.hexlify(machine.unique_id())
topic_sub = b'hello'
topic_pub = b'notification'

station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
   pass

print('Connection successful')
print(station.ifconfig())

main.py

def sub_cb(topic, msg):
    print((topic, msg))

def connect_and_subscribe():
    global client_id, mqtt_server, topic_sub
    client = MQTTClient(client_id, mqtt_server)
    client.set_callback(sub_cb)
    client.connect()
    client.subscribe(topic_sub)
    print('Connected to %s MQTT broker, subscribed to %s topic' % (mqtt_server, topic_sub))
    return client

def restart_and_reconnect():
    print('Failed to connect to MQTT broker. Reconnecting...')
    time.sleep(10)
    machine.reset()

try:
    client = connect_and_subscribe()
    client.publish(topic="hello", msg="Send By ESP32")
except OSError as e:
    restart_and_reconnect()

while True:
    try:
        new_message = client.check_msg()
        if new_message != 'None':
            client.publish(topic_pub, b'received')
        time.sleep(1)
    except OSError as e:
        restart_and_reconnect()

맥북에 MQTT 서버 설치

MQTT 서버는 상세정보는 아래 링크에서 참조 할 수 있고 주로 IOT 장비의 네트웍을 구성하는데 많이 사용한다. 나는 brew를 이용해 모스키토(Mosquitto)를 설치했다.

https://medium.com/@jspark141515/mqtt%EB%9E%80-314472c246ee

서비스 설치
brew install mosquitto

서비스 실행
brew services start mosquitto

서비스 중지
brew services stop mosquitto

맥북에서 송, 수신을 확인 할 수 있는 MQTT Client

Python용 MQTT 패키지를 먼저 설치한다.

https://pypi.org/project/paho-mqtt/

$ pip install paho-mqtt

Subscribe.py

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
print ("Connected with result code " + str(rc))
client.subscribe("hello")

def on_message(client, userdata, msg):
print ("Topic: ", msg.topic + '\nMessage: ' + str(msg.payload))

client = mqtt.Client() 
client.on_connect = on_connect 
client.on_message = on_message 
client.connect("192.168.0.7", 1883, 60) 

client.loop_forever()

Publish.py

import paho.mqtt.client as mqtt

mqttc = mqtt.Client("python_pub") 
mqttc.connect("192.168.0.7", 1883) 
mqttc.publish("hello", "Hello Billy")
mqttc.loop(2)

Publish.py를 실행하면 Subscribe.py와 ESP32에서 “Hello Billy” 메세지를 수신하는 것을 확인 할 수 있었다.