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

라즈베리파이 피코를 아두이노 와이파이 모듈 처럼 사용하기

미세먼지 측정하기

  • 저희집 고양이 호박이 화장실 모래의 미세먼지를 측정한 적이 있습니다.
  • 결과가 너무 충격적이어서 뚜껑없는 개방형 화장실로 바꿔줬었지요.
  • 그런데 겨울에 환기를 적게 하다보니, 모래 먼지가 방에 퍼져서 목이 너무 아픈거에요.
  • 그래서 먼지가 덜 나온다는 카사바 모래로 화장실 모래를 바꿔줬습니다.
  • 바꾼 모래도 먼지를 측정해 봐야겠죠?

측정 결과를 무선통신으로 보낼 수 없을까?

  • 이전에는 측정 결과를 USB 라인을 통해서 보냈습니다.
  • 그러다보니까 측정할 수 있는 공간에 한계가 생기더군요.
  • 그래서 이번에는 와이파이로 통신할 수 있는 라즈베리파이 피코에 먼지센서를 연결해 보려고 했어요.
  • 그런데 안타깝게도 3.3V 기반 ADC를 사용하는 피코에서는 값을 얻기가 어렵더군요.
  • 그래서 아두이노를 사용하되, 라즈베리파이 피코를 와이파이 모듈처럼 써서 측정값을 보내봤습니다.
    • 회로연결
  • 방법은 간단합니다, 아두이노의 시리얼핀(Tx,Rx)를 라즈베리파이 피코의 시리얼핀에 붙였어요.
  • 그런 다음 피코에서는 받아지는 값을 그대로 mqtt를 이용해서 서버로 보내기만 하도록 프로그래밍 했습니다.
광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.

보조배터리가 꺼지는 문제 해결

  • 그런데 보조배터리에 아두이노와 피코를 각각 연결하니까 1분 정도 있다가 전원이 꺼져버리는 문제가 발생하더군요.
  • 아무래도 보조배터리에 내장된 미세전류 차단 기준에 걸린것 같았습니다. 대략 60mA ~ 80mA가 그 기준값인 듯 한데요…
  • 아두이노도 피코도 따로사용할 때는 저 값보다 낮은 전류를 사용합니다.
  • 그런데 보조배터리에 있는 2개의 USB 단자에서 동시에 사용하는 전류량의 합을 기준으로 차단하는게 아니라, 둘 중에 최대값을 기준으로 차단하는 것 같아요.
  • 이로인해서 어쩔수 없이 USB 허브를 하나 투입했습니다. 이러니까 하나의 USB 단자로 약 80mA ~ 100mA를 사용하게 됩니다.
    • USB허브
  • 이제는 미세 전류 차단으로 인해 전원이 꺼지지 않고 꾸준히 동작하네요.

측정결과

  • 측정 결과는 PC에서 아래와 같이 받아보게 되었구요.
    • 결과
  • 무선으로 전송되니까 여기저기 옮겨다니면서 측정할 수 있게 되어서 좋네요.
  • 관련 에피소드는 아래 영상에서 시청할 수 있습니다~

Next.js에서 chart.js를 이용해서 그래프 그리기

지난 시간에 이어서…

  • 지난 시간에 라즈베리파이 피코로 실외에 있는 고양이 겨울집 내부의 온/습도를 측정한 다음, mqtt 통신을 통해서 원격의 구독서버가 값을 받아오는 것을 포스팅 했었습니다.
  • 오늘은 이 값을 이용해서 그래프를 그리는 페이지를 작성해 보겠습니다.

서버로그를 JSON으로 변환

  • 구독 서버가 출력하는 로그는 이런 형태인데요
2025-02-20 22:39:40 {"device_id": "d2", "humidity": 30.8, "temperature": -1.5}
2025-02-20 22:40:22 {"device_id": "d2", "humidity": 32.7, "temperature": -0.9}
2025-02-20 22:41:22 {"device_id": "d2", "humidity": 32.7, "temperature": -0.8}
  • 저기서 날짜 시간 부분을 time 프로퍼티로 추가했습니다.
  • 그리고 기상청 데이터를 기반으로 해당 시간에 기록된 실외 온도를 out 프로퍼티로 추가했습니다.
[{"time": "2025-02-20 22:39:40", "device_id": "d2", "humidity": 30.8, "temperature": -1.5,"out": -3.2},
{"time": "2025-02-20 22:40:22", "device_id": "d2", "humidity": 32.7, "temperature": -0.9,"out": -3.3},
{"time": "2025-02-20 22:41:22", "device_id": "d2", "humidity": 32.7, "temperature": -0.8,"out": -3.2}]
  • 파일의 실제 길이는 더 길겠죠??

next.js 설치

  • npm을 이용해서 next.js를 설치해주세요. 이때 타입스크립트를 이용하겠습니다.
  • 디렉토리나 인덱스 페이지와 컴포넌트 구성은 각자 알아서 해주시면 됩니다.

JSON 로딩 유틸리티 모듈

  • JSON 파일을 로딩하는 간단한 모듈을 아래와 같이 작성합니다.
import jsonData from '../data/log-0220.json'; // JSON 파일 경로

interface THItem {
    time: string;
    temperature: number;
    humidity: number;
    device_id: string;
    out: number,
}

export const getJsonData = (): THItem[] => {
    return jsonData as THItem[];
};

차트를 그려주는 컴포넌트

  • chart.js를 이용해서 그래프를 그려주는 컴포넌트를 작성했습니다.
'use client';

import React, {ComponentType} from 'react';
import {Line} from 'react-chartjs-2';
import {Chart as ChartJS, LinearScale, PointElement, LineElement, Legend} from 'chart.js';
import type {ChartData, ChartOptions} from 'chart.js';

ChartJS.register(
    LinearScale,
    PointElement,
    LineElement,
    Legend
);

interface Props {
    data: {
        time: string,
        device_id: string,
        temperature: number,
        humidity: number,
        out: number,
    }[];
}

const Chart1Client: ComponentType<Props> = ({data}) => {
    const chartData: ChartData = {
        labels: data.map((_, index) => index),
        datasets: [
            {
                label: "온도",
                data: data.map(item => item.temperature),
                borderColor: 'red',
                backgroundColor: 'rgba(255, 0, 0, 0.2)',
                tension: 0.1,
                pointRadius: 0,
                yAxisID: 'y1',
            },
            {
                label: "바깥온도",
                data: data.map(item => item.out),
                borderColor: 'orange',
                backgroundColor: 'rgba(255, 69, 0, 0.2)',
                tension: 0.1,
                pointRadius: 0,
                yAxisID: 'y1',
            },
            {
                label: "습도",
                data: data.map(item => item.humidity),
                borderColor: 'blue',
                backgroundColor: 'rgba(255, 0, 255, 0.2)',
                tension: 0.1,
                pointRadius: 0,
                yAxisID: 'y2',
            }],
    };
    const chartOptions: ChartOptions = {
        scales: {
            x: {
                title: {
                    display: true,
                    text: '',
                },
                type: 'linear',
                ticks: {
                    stepSize: 60,
                }
            },
            y1: {
                type: 'linear',
                display: true,
                position: 'left',
                title: {
                    display: true,
                    text: '온도',
                },
            },
            y2: {
                type: 'linear',
                display: true,
                position: 'right',
                grid: {
                    drawOnChartArea: false,
                },
                title: {
                    display: true,
                    text: '습도',
                },
            },
        },
    };
    return (
        <Line data={chartData} options={chartOptions}></Line>
    );
}
export default Chart1Client;
  • 그리하여 아래와 같은 차트를 그릴 수 있었습니다.
    • 그래프

시연연상

  • 이 차트를 활용한 영상도 함께 봐주시면 감사하겠습니다.

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번 핀에 연결했습니다.
    • 회로구성

시연연상

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