팀 프로젝트 #2

jjinny_0609·2023년 5월 26일
0

팀 프로젝트

목록 보기
4/4

라즈베리 파이 BMP085 센서를 이용한 데이터 수집

개발 환경

mysql
vscode 원격 터미널 사용
raspberry pi
flask web framework


개발 과정

  1. 라즈베리파이의 GPIO핀을 사용하여 BMP085(대기압 센서) 센서 연결
  2. Flask 개발 환경 구축
  3. vscode 원격 사용을 위한 환경 구축 및 라이브러리 설치
  4. MySQL Connect
  5. 차트 생성을 위한 코드 작성
  6. 데이터 측정값 확인

라즈베리파이의 GPIO핀을 사용하여 BMP085(대기압 센서) 센서 연결


위 그림과 같이 세팅해 주었다. 원격으로 작업하기 위해서 랜선을 연결하였고, 라즈베리파이에서 터미널을 열고 'ifconfig' 명령어로 아이피를 조회 후, VScode에서 Remote-SSH를 조회한 아이피를 이용해 원격으로 연결해 작업을 진행하였습니다.


Flask 개발 환경 구축

라즈베리파이에도 VScode가 설치되어 있는 환경에서 진행함을 미리 밝힙니다.

VScode가 미설치된 환경

VScode가 설치되어 있지 않다면 터미널을 열고

sudo apt install code

위 명령어를 입력하면 설치가 진행될텐데 라즈베리파이 메뉴서
'개발 - CODE - OSS' 를 클릭하면 실행됩니다.

이후 메뉴에서 File - Open Folder 클릭 이후 본인이 사용하고자 하는 폴더를 기본폴더로 지정해주시면 됩니다.


Flask

플라스크란 ?
파이썬으로 작성된 마이크로 웹 프레임워크입니다. 마이크로 웹 프레임워크란 매우 간단하고 가벼운 웹 개발 도구를 의미합니다. 작은 규모의 웹 프로젝트나 API 서비스 등을 개발하기에 적합한 도구라 Flask를 사용하게 되었습니다.


Flask 설치

  1. VSCode에서 터미널을 열고 (Ctrl + `) d
  2. 파이썬 버전먼저 확인 $ python -v
    2_1. 파이썬이 설치되어 있지 않다면.. $ sudo apt update
    apt 버전 업데이트 후
    $ sudo apt install python3
    파이썬 버전 2를 설치하려면 'python3'를 'python2'로 바꿔주시면 됩니다.
  3. 파이썬이 설치되어있으면 플라스크를 설치
    $ sudo pip install flask
    3_1. 간혹 설치가 되지않는 경우가 있는데 pip버전이 너무 낮아서 그런 것으므로 pip버전을 업그레이드 해주면 해결됩니다.
    $ sudo pip install --upgrade pip
    $ sudo pip install flask
    설치가 완료되면 4. 로 이동
  4. flask가 잘 설치되엇는지 확인하기 위해 flask를 import 해봅시다.
    설치된 것을 확인하기 위해서 파이썬 인터프리터 실행합니다.
    터미널에서 $ python 혹은 $ python3
    이후
    >>> from flask import Flask
    위 코드를 입력했는데 아무런 메시지가 없으면 정상적으로 설치된 것 입니다.

vscode 원격 사용을 위한 환경 구축 및 라이브러리 설치

VSCode 원격 개발을 하기 위해
사이드바의 "Extensions(확장)" 아이콘을 클릭합니다. 그런 다음, 검색 창에 "Remote Development"를 입력하여 "Remote Development" 확장을 찾고 설치합니다.

확장이 설치되면 왼쪽 사이드바의 "Explorer(탐색기)" 아이콘을 클릭한 다음, 상단 메뉴에서 "Remote Explorer"를 선택합니다. "SSH Targets" 섹션에서 "+" 아이콘을 클릭하고, 라즈베리 파이에 연결할 SSH 호스트 정보를 입력합니다.

라즈베리파이의 아이피를 이용해 호스트 구성 설정을 해줍니다.


MySQL Connect

  1. 먼저, 라즈베리 파이에 MySQL을 설치합니다. 터미널에서 다음 명령어를 실행하여 MySQL을 설치합니다.
sudo apt update
sudo apt install mysql-server
  1. MySQL 설치가 완료되면, MySQL 서버를 시작합니다.
$ sudo systemctl start mysql
  1. MySQL에 액세스하기 위해 MySQL 클라이언트를 설치합니다.
sudo apt install mysql-client
  1. Python에서 MySQL에 연결하기 위해 mysql-connector-python 라이브러리를 설치합니다. 터미널에서 다음 명령어를 실행합니다.
pip install mysql-connector-python
  1. 이제 파이썬 스크립트에서 MySQL에 연결하여 작업할 수 있습니다. 다음과 같은 코드를 작성하여 MySQL에 연결하고 쿼리를 실행하여 연결해보자.

[connect_test.py] 작성

from flask.json import jsonify
import mysql.connector
import Adafruit_BMP.BMP085 as BMP085
import time
import threading
from flask import Flask, render_template


# MySQL 서버 연결 설정(mysql.connector 모듈 사용)
db = mysql.connector.connect(
    host='HOST 정보(ip)',
    user='USER 작성',
    password='PASSWORD 작성',
    database='데이터베이스명 작성'
)

cursor = db.cursor()

# BMP180 센서 객체 생성(이 센서를 통해 온도, 압력, 고도 값을 측정)
sensor = BMP085.BMP085(busnum=1)

# Flask 애플리케이션 생성
app = Flask(__name__)

# 서버에 값 전달
@app.route('/update_sensor_data') # 센서 데이터를 JSON 형식으로 반환
def update_sensor_data():
    temp = sensor.read_temperature()
    pressure = sensor.read_pressure()
    altitude = sensor.read_altitude()
    sensor_data = {'temp': temp, 'pressure': pressure, 'altitude': altitude}
    return jsonify(sensor_data)

# 루트 경로 처리
@app.route('/')
def home():
    return render_template('html/home.html')    # render_template 함수를 사용해서 HTML 파일을 렌더링

# 데이터 측정 및 저장 함수 ,BMP180 센서를 통해 값을 측정하고, 현재 시간과 함께 DB에 데이터를 저장
def measure_and_store_data():
    try:
        temp = sensor.read_temperature()
        pressure = sensor.read_pressure()
        altitude = sensor.read_altitude()
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')

        # 데이터베이스에 데이터 저장
        query = "INSERT INTO sensor_data(temp, pressure, altitude, timestamp) VALUES (%s, %s, %s, %s)"
        values = (temp, pressure, altitude, timestamp)
        cursor.execute(query, values)
        db.commit()
        print("데이터 저장 완료")

    except mysql.connector.Error as error:
        print("MySQL 예외 발생:", error)

# 초기 데이터 저장
measure_and_store_data()

# 추가적인 데이터 측정 및 저장
@app.before_first_request
def start_measurement():
    def run_measurement():
        while True:
            measure_and_store_data()
            time.sleep(5)  # 5초 간격으로 측정

    measurement_thread = threading.Thread(target=run_measurement)
    measurement_thread.start()

# Flask 애플리케이션 실행
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)  # debug=True로 설정하면 개발 모드에서 디버그 정보가 표시

# 연결 종료
cursor.close()
db.close()

차트 생성을 위한 코드 작성

[chart-line-demo.js] 작성

// Set new default font family and font color to mimic Bootstrap's default styling
Chart.defaults.global.defaultFontFamily = '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';

// Area Chart Example(기온)
var ctx = document.getElementById("myAreaChart");   // HTML 요소를 선택하여 차트를 그리는 컨텍스트
var myLineChart = new Chart(ctx, {
  type: 'line', // 타입 지정 : 선
  data: {
    labels: [], // 배열 x축의 레이블 값을 나타냄, 빈 배열로 설정
    datasets: [{
      label: "Sessions",    // label은 데이터셋의 라벨을 지정
      lineTension: 0.3,     // lineTension은 선의 곡선 정도를 나타냄
      backgroundColor: "rgba(2,117,216,0.2)",   // 그래프 영역의 배경색을 설정
      borderColor: "rgba(2,117,216,1)",     // 선의 색상을 설정
      pointRadius: 5,   // 데이터 포인트 점의 크기
      pointBackgroundColor: "rgba(2,117,216,1)",    // 포인트의 배경색
      pointBorderColor: "rgba(255,255,255,0.8)",    // 포인트의 테두리 색
      pointHoverRadius: 5,                          // 마우스를 올렸을때 포인트의 크기
      pointHoverBackgroundColor: "rgba(2,117,216,1)",   // 포인트에 마우스를 올렸을 때 배경색
      pointHitRadius: 50,   // 포인트와의 거리를 판단하는 반경
      pointBorderWidth: 2,  // 포인트의 테두리 너비
      data: [], // data배열은 초기에 빈 배열로 설정, 그래프의 실제 데이터를 담는다.
    }],
  },
  options: {    // 옵션 설정
    animation: {    // 그래프의 애니메이션 효과를 제어
      duration: 0, // 애니메이션의 지속 시간을 0으로 설정하여 애니메이션을 비활성화합니다.
    },
    scales: {   // 객체는 x축과 y축의 스케일(눈금)을 설정
      xAxes: [{ // xAxes 배열은 x축의 설정을 나타냅니다.
        time: { // x축의 시간 관련 설정을 정의
          unit: 'date'  // x축의 레이블이 날짜 형식임을 나타냄
        },
        gridLines: {    // 객체는 그리드 라인의 표시 여부를 설정
          display: false    // false : 그리드 라인을 숨김
        },
        ticks: {    // x축의 눈금 설정을 지정
          maxTicksLimit: 7  // 표시할 최대 눈금 개수를 나타냄
        }
      }],
      yAxes: [{     // 배열의 y축 설정을 나타냄
        ticks: {    // y축의 눈금 설정을 지정
          min: 0,   // y축의 최소값
          max: 50,  // y축의 최대값
          maxTicksLimit: 10 // 표시할 최대 눈금 개수를 나타냄
        },
        gridLines: {    // y축 그리드 라인 색상을 설정
          color: "rgba(0, 0, 0, .125)",
        }
      }],
    },
    legend: {
      display: false    // 범례 설정을 제어, false : 표시하지않음
    }
  }
});

// 함수를 정의하여 데이터 값 업데이트 및 차트 업데이트를 호출하는 부분을 만듦
function updateChart() {
  // 서버에 센서 데이터 요청을 보냄
  fetch('http://localhost:5000/update_sensor_data')
    .then(response => response.json())  // 응답 데이터를 처리
    .then(data => {
      const newData = data.temp; // 새로운 데이터
      const labels = myLineChart.data.labels;   // 'myLineChart.data.labels'는 차트의 레이블 배열을 나타냄
      const datasetData = myLineChart.data.datasets[0].data;    // 'myLineChart.data.datasets[0].data'는 데이터셋의 데이터 배열을 나타냄

      // 데이터 길이를 5개로 유지하도록 제한
      if (labels.length === 5) {
        labels.shift(); // 레이블의 첫 번째 값을 제거
        datasetData.shift(); // 데이터셋의 첫 번째 값을 제거
      }

      labels.push(new Date().toLocaleTimeString()); // 현재 시간 추가
      datasetData.push(newData); // 새로운 데이터 추가

      // 차트를 업데이트합니다.
      myLineChart.update();
    });
}

// 일정한 간격으로 updateChart 함수를 호출하는 타이머를 설정 (10초마다)
// setInterval(updateChart, 500); 기존 바로 시작

  var stopButton = document.getElementById("stopButton");
  var startButton = document.getElementById("startButton");
  var timerId;

  // 정지 버튼 클릭 시 데이터 업데이트 타이머를 멈춥니다.
  stopButton.addEventListener("click", function() {
    clearInterval(timerId);
  });

  // 시작 버튼 클릭 시 데이터 업데이트 타이머를 다시 시작합니다.
  startButton.addEventListener("click", function() {
    timerId = setInterval(updateChart, 500);
  });

2개의 차트를 작동시킬 코드도 같은 방식으로 만들어 준다. 코드의 방식은 비슷하다.

[chart-line1-demo.js] 작성

// Set new default font family and font color to mimic Bootstrap's default styling
Chart.defaults.global.defaultFontFamily = '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';

// Area Chart Example(고도)
var ctx = document.getElementById("myAreaChart2");
var myLineChart2 = new Chart(ctx, {
  type: 'line',
  data: {
    labels: [],
    datasets: [{
      label: "Sessions",
      lineTension: 0.3,
      backgroundColor: "rgba(2,117,216,0.2)",
      borderColor: "rgba(2,117,216,1)",
      pointRadius: 5,
      pointBackgroundColor: "rgba(2,117,216,1)",
      pointBorderColor: "rgba(255,255,255,0.8)",
      pointHoverRadius: 5,
      pointHoverBackgroundColor: "rgba(2,117,216,1)",
      pointHitRadius: 50,
      pointBorderWidth: 2,
      data: [],
    }],
  },
  options: {
    animation: {
      duration: 0, // 애니메이션의 지속 시간을 0으로 설정하여 애니메이션을 비활성화합니다.
    },
    scales: {
      xAxes: [{
        time: {
          unit: 'date'
        },
        gridLines: {
          display: false
        },
        ticks: {
          maxTicksLimit: 7
        }
      }],
      yAxes: [{
        ticks: {
          min: -40,
          max: 40,
          maxTicksLimit: 30
        },
        gridLines: {
          color: "rgba(0, 0, 0, .125)",
        }
      }],
    },
    legend: {
      display: false
    }
  }
});

//함수를 정의하여 데이터 값 업데이트 및 차트 업데이트를 호출하는 부분을 만듦
function updateChart2() {
  // 서버에 센서 데이터 요청을 보냄
  fetch('http://localhost:5000/update_sensor_data')
    .then(response => response.json())
    .then(data => {
      const newData = data.altitude; // 새로운 데이터
      const labels = myLineChart2.data.labels;
      const datasetData = myLineChart2.data.datasets[0].data;

      // 데이터셋의 데이터 값을 업데이트
      if (labels.length === 5) {
        labels.shift(); // 레이블의 첫 번째 값을 제거
        datasetData.shift(); // 데이터셋의 첫 번째 값을 제거
      }

      labels.push(new Date().toLocaleTimeString()); // 현재 시간 추가
      datasetData.push(newData); // 새로운 데이터 추가

      // 빈 공간이 아닌 경우에만 차트를 업데이트합니다.
        myLineChart2.update();
    });
}

// 일정한 간격으로 updateChart 함수를 호출하는 타이머를 설정 (10초마다)
// setInterval(updateChart2, 500); 기존 바로 시작

  var stopButton = document.getElementById("stopButton");
  var startButton = document.getElementById("startButton");
  var timerId2;

  // 정지 버튼 클릭 시 데이터 업데이트 타이머를 멈춥니다.
  stopButton.addEventListener("click", function() {
    clearInterval(timerId2);
  });

  // 시작 버튼 클릭 시 데이터 업데이트 타이머를 다시 시작합니다.
  startButton.addEventListener("click", function() {
    timerId2 = setInterval(updateChart2, 500);
  });

[chart-line2-demo.js] 작성

// Set new default font family and font color to mimic Bootstrap's default styling
Chart.defaults.global.defaultFontFamily = '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';

// Area Chart Example,규선
var ctx = document.getElementById("myAreaChart3");
var myLineChart3 = new Chart(ctx, {
  type: 'line',
  data: {
    labels: [],
    datasets: [{
      label: "Sessions",
      lineTension: 0.3,
      backgroundColor: "rgba(2,117,216,0.2)",
      borderColor: "rgba(2,117,216,1)",
      pointRadius: 5,
      pointBackgroundColor: "rgba(2,117,216,1)",
      pointBorderColor: "rgba(255,255,255,0.8)",
      pointHoverRadius: 5,
      pointHoverBackgroundColor: "rgba(2,117,216,1)",
      pointHitRadius: 50,
      pointBorderWidth: 2,
      data: [],
    }],
  },
  options: {
    animation: {
      duration: 0, // 애니메이션의 지속 시간을 0으로 설정하여 애니메이션을 비활성화합니다.
    },
    scales: {
      xAxes: [{
        time: {
          unit: 'date'
        },
        gridLines: {
          display: false
        },
        ticks: {
          maxTicksLimit: 7
        }
      }],
      yAxes: [{
        ticks: {
          min: 0,
          max: 150000,
          maxTicksLimit: 10
        },
        gridLines: {
          color: "rgba(0, 0, 0, .125)",
        }
      }],
    },
    legend: {
      display: false
    }
  }
});

//함수를 정의하여 데이터 값 업데이트 및 차트 업데이트를 호출하는 부분을 만듦
function updateChart3() {
  // 서버에 센서 데이터 요청을 보냄
  fetch('http://localhost:5000/update_sensor_data')
    .then(response => response.json())
    .then(data => {

       // 데이터셋의 데이터 값을 업데이트
       myLineChart3.data.datasets[0].data.push(data.pressure);
       myLineChart3.data.labels.push(new Date().toLocaleTimeString()); // 현재 시간 추가
 
       // 데이터 길이를 5개로 유지하도록 제한
       if (myLineChart3.data.datasets[0].data.length > 5) {
         myLineChart3.data.datasets[0].data.shift();
         myLineChart3.data.labels.shift();
       }

      // 차트를 업데이트합니다.
      myLineChart3.update();
    });
}

// 일정한 간격으로 updateChart 함수를 호출하는 타이머를 설정 (10초마다)
// setInterval(updateChart3, 500);

var stopButton = document.getElementById("stopButton");
var startButton = document.getElementById("startButton");
var timerId3;

// 정지 버튼 클릭 시 데이터 업데이트 타이머를 멈춥니다.
stopButton.addEventListener("click", function() {
  clearInterval(timerId3);
});

// 시작 버튼 클릭 시 데이터 업데이트 타이머를 다시 시작합니다.
startButton.addEventListener("click", function() {
  timerId3 = setInterval(updateChart3, 500);
});


데이터 측정값 확인

프로젝트 완성본

ERD

MYSQL에 저장된 값 조회

프로젝트를 진행하면서 아쉬웠던 점은 애플 워치를 사용하여 나의 심박수를 실시간으로 조회하는 기능을 구현하지 못한 것입니다. 이 기능은 원하였으나, 제한된 시간과 예비군 훈련까지 겹쳐 구현하지 못한 점이 아쉬웠습니다.

그래도 맥북을 사서 스위프트를 경험해볼 수 있어서 좋았음..!

profile
뉴비 개발자 입니다. velog 주소 : https://velog.io/@jjinny_0609 Github 주소 :

0개의 댓글