하루에 5분씩만 투자해서 함께 공부해요~ 댓글은 개별 포스트 클릭하면 작성가능합니다~

Mosquitto와 라즈베리파이 피코를 이용해서 실외 온도 측정하기

라즈베리파이 피코 + DHT22(AM2320) 온습도 센서모듈

  • 야외의 온도를 측정하기 위해서는 저전력 IoT 기술이 필요합니다.
  • 다행스럽게돋 라즈베리파이 피코는 저전력이면서 상대적으로 훌륭한 연산능력을 보여주죠, 게닫가 Wifi 통신도 가능합니다.
    • PICO
  • 여기에 온습도 센서모듈을 연결하여 Wifi로 통신을 하면 실외온도 측정이 가능해집니다.
    • DHT22
  • 교육용 아두이노 키트에 함께 들어있는 DHT11 센서는 영하의 온도는 측정이 불가능합니다. 그래서 저는 DHT22 센서가 들어간 모듈을 이용했습니다.

마이크로 파이선 그리고 AM2320 라이브러리 설치

  • 라즈베리파이 피코는 마이크로 파이선을 이용해서 코드를 작성할 수 있는데요, 온도센서 모듈을 이용하기 위해서는 AM2320 라이브러리를 추가적으로 설치해야 합니다.
  • 그런데 동작하는 라이브러리를 찾기가 어려웠어요. 이게 맞다 저게 맞다 하는 외국 사이트의 포스팅들을 뒤져보다가 제가 찾아낸 것은 바로 아래 링크의 소스코드 입니다.

Mosquitto 그리고 MQTT

  • HTTP 서버를 만들고 피코에서 request를 보내서 온도를 확인하는 방법도 있습니다만
  • MQTT 통신을 이용하면 전력효율이 더 높다고 하기 때문에, 서버 PC에 mosquitto를 설치했습니다.

TypeScript MQTT 구독 서버

  • 온도값을 구독하기 위한 간단한 서버를 TypeScript를 이용해서 작성합니다.
import * as mqtt from 'mqtt';

const client = mqtt.connect('mqtt://IP:PORT', {keepalive: 3600});

client.on('connect', () => {
    console.log('connected');
    client.subscribe('구독토픽');
});

client.on('message', (topic: String, message: Buffer) => {
    console.log(topic + ' ' + message.toString());
});

client.on('error', (err: Error)=> {
    console.error(err);
    client.end();
});

client.on('close', () => {
    console.log('disconnected');
});
광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.

마이크로 파이선 발행 코드

  • 이제 피코에 들어갈 발해용 서버를 작성하겠습니다. 구현에 앞서서 mqtt 라이브러리인 설치합니다.
import mip
mip.install('umqtt.simple')
  • 코드는 아래와 같습니다.
import utime
import rp2 
from machine import I2C, Pin
import network
import time
import urequests as requests
import am2320
import ujson
import umqtt.simple

mqtt_broker_ip = "IP at mosquitto"
client_id = "d2"
ssid = '공유기 ID'
password = '공유기 비번'

def send_message_mqtt(client, h,t):
    print("Publish Mqtt Message")
    msg = ujson.dumps({'device_id': client_id, 'temperature': t, 'humidity': h})
    try:
        client.publish("구독토픽", msg)
    except OSError as e:
        print(e)
    
def wlan_connect():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)

    # Setup onboard LED
    onboard_led = Pin('LED', Pin.OUT)  # Adjust the pin number if necessary

    # Initial connection attempt
    max_attempts = 10
    attempts = 0
    connected = False  # Flag to track connection status

    while not wlan.isconnected() and attempts < max_attempts:
        onboard_led.toggle()  # Blink LED while trying to connect
        time.sleep(1)
        attempts += 1
        
    if wlan.isconnected():
        print(f"Connected to {ssid} successfully!")
        print("Network Config:", wlan.ifconfig())
        onboard_led.value(1)  # Keep LED on when connected
        connected = True
    else:
        print("Failed to connect to WiFi.")
        onboard_led.value(0)  # Turn off LED if initial connection fails
    return [wlan, connected]

def mqtt_connect():
    print("Try Mqtt Connect")
    client = umqtt.simple.MQTTClient(client_id = client_id, server = mqtt_broker_ip, port = #MQTT포트)
    client.connect()
    return client

def am2320_connect():
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000)
    sensor = am2320.AM2320(i2c)

    if sensor.check():
        print(f"AM2320 found at I2C address {am2320.I2C_ADDRESS:#x}")
    
    return sensor

        
def main_mqtt():
    wret = wlan_connect()
    wlan = wret[0]
    connected = wret[1]
    client = 0
    utime.sleep(3)
    try:
        client = mqtt_connect()
    except OSError as e:
        print(f"Error: %s" % e)
    print("Mqtt Connect Done")
    
    step1 = 2
    utime.sleep(1)

    sensor = am2320_connect()
        
    while True:
        if step1 == 2:
            print('reading')
            total=0
            sensor.measure()
            humidity = sensor.humidity()
            temperature = sensor.temperature()
            
            print("M2 Humidity: %d%%, Temp: %dC" % (humidity, temperature))        
                
            if wlan.isconnected() and not connected:
                print("Reconnected to WiFi.")
                onboard_led.value(1)  # Keep LED on when connected
                print("Network Config:", wlan.ifconfig())
                connected = True
            elif not wlan.isconnected() and connected:
                print("Disconnected from WiFi.")
                connected = False

            if not connected:
                print("Wlan Not Connected")
                onboard_led.toggle()  # Blink LED if disconnected
            else:
                if client == 0:
                    print("MQTT is not connected")
                else:
                    send_message_mqtt(client, humidity, temperature)                
                
            step1 = 1
        else:
            step1 = step1 + 1
        
        utime.sleep(30)

main_mqtt()
  • 위 코드는 1분에 한번씩 온습도를 측정해서 데이터를 발행합니다.
  • 서버PC에 mosquitto가 실행중이고, 구독서버가 돌아가고 있다면 발행된 정보가 보여지게 됩니다.
  • AM2320 회로구성시, SCL은 피코의 17번 SDL은 피코의 16번 핀에 연결했습니다.
    • 회로구성

시연연상

  • 완성된 시스템을 이용해서 온도측정에 성공한 영상 입니다.
  • 영상

Google STT와 Gemini를 이용해서 캘린더에 음성으로 스케쥴 추가하기

HeyMySec

  • GitHub Respository
  • 음성으로 구글 캘린더에 스케쥴을 입력할 수 있는 어플리케이션 입니다.
  • Flutter를 이용해서 개발했습니다.
  • 안드로이드 버젼에 한해서 실행가능 합니다.
  • 구글 로그인, 구글 캘린더 및 Gemini API를 이용합니다.

개발하게 된 계기

  • 아래 글에 개발하게 된 이유가 설명되어 있습니다.
    • https://blog.naver.com/dolja21/223392231706
  • 위와 같은 이유로 자연어 문장으로 이루어진 스케쥴 정보를 구글 캘린더에 정확하게 입력하는 앱을 구상하게 되었습니다.
  • 처음에는 자연어 처리를 위한 AI 솔루션이 백엔드 서버로 존재하고, 앱에서는 파이어베이스 Realtime Database에 요청을 추가하면 백엔드 서버에서 배치작업으로 스케쥴 정보를 인식한 후 이를 다시 앱에 전달하는 방식으로 구현 했었습니다.
  • 그런데 이 방식은 관리하는데 들어가는 비용측면, 그리고 여기에 사용한 슬롯필링, NER 등의 AI 모델의 정확성 문제로 인해서 지금의 방식으로 전환하였습니다.
  • 지금은 위에서 구상했던 백엔드 서버가 해야 할 작업을 구글 Gemini(LLM) 서비스에 맡기고 있습니다.

실행하기 전에 필요한 것

  • 구글 로그인 및 캘린더 API에 접근 가능한 계정 및 google-services.json
    • android/app 폴더에 복사해야 합니다.
  • 구글 Gemini API 키 : main.dart 에서 이에 해당하는 부분에 키를 입력해야 합니다

실행화면

  • 사용법은 간단합니다.
  • 먼저 구글 계정으로 로그인 합니다.
    • Login
  • 권한 지정
    • 마이크 사용을 위한 권한을 허용해야 합니다.
    • Login
  • 음성 인식 시작
    • 녹음 버튼을 터치 해서 메시지를 남겨보세요
    • Login
    • 멈춤 버튼을 터치 하여 메시지 녹음을 중단하고, 인식된 문장을 확인합니다.
    • Login
    • 재녹음 하고 싶으면 다시 녹음 버튼을 터치하면 됩니다.
    • Login
    • 그게 아니고 스케쥴 입력을 진행하고 싶으면 보내기 버튼을 터치합니다.
  • 정보 확인
    • 스케쥴을 입력하기 전에, 마지막으로 자신이 요청한 정보가 맞는지 확인합니다.
    • Login
    • 구글 캘린더에 들어가보면 스케쥴이 새로 만들어진 것을 확인할 수 있습니다.
    • Login

사용된 기술

  • 구글 로그인 및 캘린더 API
  • 구글 Gemini API
  • 구글 STT

앞으로의 계획

  • UI/UX 개선
  • STT 인식 정화도 개선
  • 카테고리명 띄어쓰기 반영
  • 스케쥴 수정 기능 추가

Flutter에서 느려지는 현상 없는 폴더선택 다이얼로그 만들기

  • 요즘 스마트폰 용량이 TB급으로 늘어나면서, 사진을 찍어놓고 PC에 옮기지 않는 분들이 많을겁니다.
  • 사진 폴더에 파일이 많다보니 폴더 내부에 파일을 로딩할 때 시간이 오래걸리게 됩니다.
  • 저 같은 경우에 유튜브에서 사용하기 위한 영상을 PC로 옮기려다 보니, 이럴 때 지연되는 시간이 아깝더라구요.
  • 그래서 사진을 년/월/일 별로 폴더화 해서 저장해서 필요한 폴더만 PC로 옮기곤 합니다.
  • 그런데 이것도 매번 수동으로 하려고 하니까 귀찮더군요, 그래서 폴더로 저장하는 것을 자동화 하는 앱을 만들어 봤어요.
  • 하지만 PC에서 느려지는 문제는 당연히 스마트폰에서도 똑같이 발생하더군요
  • 자동화해서 옮길 대상 폴더를 선택하는 다이얼로그도 결국 해당 폴더에 있는 파일 정보를 훑어야 표출할 수 있기 때문에, 결국 느려지는 문제는 계쇡되게 됩니다.
  • 이 문제를 해결하기 위해서 여러 유형의 폴더선택 다이얼로그들을 찾아봤는데요…
    • directory.dart 에 존재하는 listSync 함수를 이용해서 파일 리스트를 가져오는 것만 있더군요.
    • 이 함수는 모든 정보를 가져올 때 까지 기다려야 하기 때문에, 폴더내에 파일이 많으면 느려지는 현상이 발생합니다.
    • 저는 listSync 함수가 아닌 list 함수를 이용해 봤어요.
    • list 함수는 stream을 이용하기 때문에, ListView.builder 와 연결해서 파일 정보를 가져올 때 마다 state를 수정하면서 갱신하면 됩니다. 이러면 지연 현상이 발생하지 않습니다.
  • 주요 코드부는 아래와 같습니다.
dstream = directory!.list();
dstream.listen((item) {
  debugPrint(item.path);
  if (FileSystemEntity.isDirectorySync(item.path)) {
    setState(() {
      directories.add(item);
    });
  }
}, onDone: () {
  setState(() {
    debugPrint("Done Path Find");
    isDone = true;
  });
});
  • 이때 다이얼로그를 구성하는 코드는 아래 레포지토리를 참고했어요.
    • Easy-Folder-Picker
    • InheritedWidget과 WidgetsBindingObserver를 이용한 디자인 패턴이 상당히 효과적이더라구요.
    • 물론 이 패키지도 listSync를 쓰기 때문에 지연현상은 발생합니다.
  • 제가 만든 어플의 레포지토리는 아래 링크입니다.