라즈베리 파이 BMP085 센서를 이용한 데이터 수집
mysql
vscode 원격 터미널 사용
raspberry pi
flask web framework
위 그림과 같이 세팅해 주었다. 원격으로 작업하기 위해서 랜선을 연결하였고, 라즈베리파이에서 터미널을 열고 'ifconfig' 명령어로 아이피를 조회 후, VScode에서 Remote-SSH를 조회한 아이피를 이용해 원격으로 연결해 작업을 진행하였습니다.
라즈베리파이에도 VScode가 설치되어 있는 환경에서 진행함을 미리 밝힙니다.
VScode가 설치되어 있지 않다면 터미널을 열고
sudo apt install code
위 명령어를 입력하면 설치가 진행될텐데 라즈베리파이 메뉴서
'개발 - CODE - OSS' 를 클릭하면 실행됩니다.
이후 메뉴에서 File - Open Folder 클릭 이후 본인이 사용하고자 하는 폴더를 기본폴더로 지정해주시면 됩니다.
플라스크란 ?
파이썬으로 작성된 마이크로 웹 프레임워크입니다. 마이크로 웹 프레임워크란 매우 간단하고 가벼운 웹 개발 도구를 의미합니다. 작은 규모의 웹 프로젝트나 API 서비스 등을 개발하기에 적합한 도구라 Flask를 사용하게 되었습니다.
$ python -v
$ sudo apt update
$ sudo apt install python3
$ sudo pip install flask
$ sudo pip install --upgrade pip
$ sudo pip install flask
$ python
혹은 $ python3
>>> from flask import Flask
VSCode 원격 개발을 하기 위해
사이드바의 "Extensions(확장)" 아이콘을 클릭합니다. 그런 다음, 검색 창에 "Remote Development"를 입력하여 "Remote Development" 확장을 찾고 설치합니다.
확장이 설치되면 왼쪽 사이드바의 "Explorer(탐색기)" 아이콘을 클릭한 다음, 상단 메뉴에서 "Remote Explorer"를 선택합니다. "SSH Targets" 섹션에서 "+" 아이콘을 클릭하고, 라즈베리 파이에 연결할 SSH 호스트 정보를 입력합니다.
라즈베리파이의 아이피를 이용해 호스트 구성 설정을 해줍니다.
sudo apt update
sudo apt install mysql-server
$ sudo systemctl start mysql
sudo apt install mysql-client
pip install mysql-connector-python
[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);
});
프로젝트를 진행하면서 아쉬웠던 점은 애플 워치를 사용하여 나의 심박수를 실시간으로 조회하는 기능을 구현하지 못한 것입니다. 이 기능은 원하였으나, 제한된 시간과 예비군 훈련까지 겹쳐 구현하지 못한 점이 아쉬웠습니다.
그래도 맥북을 사서 스위프트를 경험해볼 수 있어서 좋았음..!