[실습] IoT 웹 사용자 인터페이스
필기자
2024-04-01 13:20
2,582
0
-
- 첨부파일 : iot.zip (3.5M) - 다운로드
본문
IoT 웹 사용자 인터페이스
※ 첨부 파일은 html과 static만 포함함. python은 아래 코드로 생성하여야 함.
목 적
1. 웹 서비스를 위한 파이썬 프로그램 개발
2. 웹 서비스를 위한 프론트(html,js , css) 프로그램 개발
※ 현재(가상환경)에서 설치된 파이썬 라이브러리 목록 가져오고 새로운 환경에서 설치하기
※ 첨부 파일은 html과 static만 포함함. python은 아래 코드로 생성하여야 함.
목 적
- 라즈베리파이에서 엑츄레이터 및 센서 제어를 위한 user interface를 작성한다.
- 라즈베리파이에서 http, socket.io 통신을 활용하여 웹 서비스를 제공한다.
1. 웹 서비스를 위한 파이썬 프로그램 개발
2. 웹 서비스를 위한 프론트(html,js , css) 프로그램 개발
2. 웹 서비스를 위한 프론트(html,js , css) 프로그램 개발
1. 웹 서비스를 위한 파이썬 프로그램 개발
- common.py
- aiohttp, python-socketio, jinja2 등을 활용하기 위한 초기화, 웹 서비스를 위한 공통 함수를 정의.
- aiohttp
- asyncio 라이브러리를 사용해 비동기 I/O를 지원하는 HTTP 클라이언트/서버 프레임워크.
- python-socketio
- Python에서 Socket.IO 프로토콜을 구현한 라이브러리.
- Socket.IO : 실시간, 양방향, 이벤트 기반 통신을 가능하게 해주 웹 애플리케이션 기술.
- python-socketio는 Socket.IO 프로토콜을 사용하여 서버와 클라이언트 간의 실시간 통신 기능을 Python 애플리케이션에 제공함.
- jinja2
- Python에서 널리 사용되는 텍스트 템플릿 엔진.
- HTML, XML 파일을 생성할 때 사용되며, 템플릿 상속, 매크로 등의 기능을 지원해 코드 재사용성과 가독성이 높음.
- Flask와 같은 웹 프레임워크에서 기본 템플릿 엔진으로 사용됨.
import psutil
import subprocess
import aiohttp
import genId
from aiohttp import web
from aiohttp_session import get_session
import socketio
from jinja2 import Environment, FileSystemLoader
from setting import SETTING
app = aiohttp.web.Application()
sio = socketio.AsyncServer(cors_allowed_origins='*')
sio.attach(app) #http와 socket.io 통합
async def session_check(request):
session = await get_session(request)
if 'authenticated' not in session:
raise web.HTTPFound('/login')
return session
def response_html(page, data=None):
global SETTING
try:
rand = genId.generate_hash()
template_loader = FileSystemLoader('html')
template_env = Environment(loader=template_loader)
template = template_env.get_template(page)
if data:
rendered_template = template.render(system_mode=SETTING['SYSTEM_MODE'], rand=rand, data=data)
else:
rendered_template = template.render(system_mode=SETTING['SYSTEM_MODE'], rand=rand)
return web.Response(text=rendered_template, content_type='text/html')
except Exception as e:
# 오류 발생 시의 응답
return aiohttp.web.Response(text=str(e), status=500)
''' CPU 온도를 얻는 함수 '''
def get_cpu_temperature():
with open('/sys/class/thermal/thermal_zone0/temp') as f:
temp = f.read()
temp = int(temp) / 1000 # millidegree to degree
return temp
''' 디스크 사용량을 얻는 함수 '''
def get_disk_usage():
try:
df_output = subprocess.check_output(['df', '/home'], text=True)
lines = df_output.strip().split('\n')
if len(lines) >= 2:
fields = lines[1].split()
if len(fields) >= 5:
total_disk = int(fields[1]) * 1024 # 1K 블록 단위
used_disk = int(fields[2]) * 1024
disk_percent = float(fields[4].replace('%', ''))
disk_free = int(fields[3]) * 1024
return total_disk, used_disk, disk_percent, disk_free
except Exception as e:
print(f"An error occurred: {e}")
return None, None, None, None
''' 시스템 전방적인 사용량 확인 함수 '''
def get_system_info():
cpu_percent = psutil.cpu_percent()
memory = psutil.virtual_memory()
total_memory = memory.total
used_memory = memory.used
memory_percent = memory.percent
cpu_temp = get_cpu_temperature()
total_disk, used_disk, disk_percent, disk_free = get_disk_usage()
system_info = {
'cpu_percent': cpu_percent,
'total_memory': total_memory,
'used_memory': used_memory,
'memory_percent': memory_percent,
'total_disk': total_disk,
'used_disk': used_disk,
'disk_percent': disk_percent,
'cpu_temp': cpu_temp
}
return system_info
- request_handlers.py
- http 요청을 처리하는 함수 모음
from common import response_html
async def mainHandle(request):
return response_html('index.html')
- socket_events.py
- socketio(서버) 요청을 처리하는 함수 모음
from common import get_system_info, sio # common.py에서 get_system_info 함수, sio 변수를 가져옵니다.
@sio.event
async def connect(sid, environ):
print('클라이언트 연결', sid)
@sio.event
async def disconnect(sid):
print('클라이언트 종료', sid)
@sio.on('get_system_info')
async def on_get_system_info(sid, data):
systemInfo = get_system_info()
await sio.emit('ret_system_info', systemInfo, room=sid)
- setting.py
- 프로그램 초기 상수 모음
SETTING = {}
SETTING['SYSTEM_MODE'] = 'admin'
SETTING['SECRET_KEY'] = ''
SETTING['COOKIE_NAME'] = 'iot_server'
- genId.py
- 랜덤 값, 교유값을 만듬
import os
import random
import hashlib
import time
def generate_hash():
timestamp = str(int(time.time() * 1000000)) # 현재 시간값을 마이크로초 단위로 변환
rand_str = ''.join(random.choice('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(16))
return hashlib.sha256((timestamp + rand_str).encode('utf-8')).hexdigest()[:8]
def generate_client_id():
if os.path.exists("client_id.txt"):
# 파일이 존재한다면 파일을 읽어서 변수에 저장
with open("client_id.txt", "r") as f:
client_id = f.read().strip()
else:
# 파일이 존재하지 않는다면 새로운 id 생성
# 현재 시간값을 기반으로 랜덤한 문자열 생성 후 sha256 해시 적용
client_id = generate_hash()
# 생성한 client_id를 파일에 저장
with open("client_id.txt", "w") as f:
f.write(client_id)
return client_id
def generate_admin_id():
if os.path.exists("admin.txt"):
# 파일이 존재한다면 파일을 읽어서 변수에 저장
with open("admin.txt", "r") as f:
admin_id = f.read().strip()
else:
admin_id = "admin:PCT00000"
# 생성한 admin_id를 파일에 저장
with open("admin.txt", "w") as f:
f.write(admin_id)
return admin_id
- iot_server.py
- 메인 프로그램, http 및 socketio 실행
from setting import SETTING
#import socket
#import socketio #pip install python-socketio
import secrets
import asyncio
#import aiohttp #pip install aiohttp
#from aiohttp import web
from aiohttp_session import setup #pip install aiohttp_session
from aiohttp_session.cookie_storage import EncryptedCookieStorage #pip install cryptography
#from jinja2 import Environment, FileSystemLoader #pip install Jinja2
import json
#import genId #pip install genId
#import psutil #pip install psutil
#import subprocess
from common import web, app # socket_events.py에서 sio 인스턴스를 가져옵니다.
from request_handlers import mainHandle # request_handlers.py에서 mainHandle 함수를 가져옵니다.
import socket_events
# 암호화 키 설정
SETTING['SECRET_KEY'] = secrets.token_bytes(32)
# 세션 미들웨어 설정
setup(app, EncryptedCookieStorage(SETTING['SECRET_KEY'], cookie_name=SETTING['COOKIE_NAME']))
async def web_server():
app.router.add_static('/static/', path='static/', name='static')
app.router.add_get('/', mainHandle)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, '0.0.0.0', 5000) # http://본인아이피:5000
await site.start()
async def main():
try:
await web_server() # 웹 서버 시작
# 무한 루프로 서버가 계속 실행되도록 유지
while True:
await asyncio.sleep(3600) # 예시로, 1시간마다 대기를 풀고 다시 대기함
except KeyboardInterrupt:
print("프로그램이 사용자에 의해 종료됨.")
except Exception as e:
print(f"예외 발생: {e}")
if __name__ == '__main__':
asyncio.run(main())
2. 웹 서비스를 위한 프론트(html,js , css) 프로그램 개발
- HTML 구조
- https://github.com/silverbux/bootflat-admin 사용
- index.html : 메인화면 top.html, system.html, menu.html을 포함하며 부트스트랩3, jquery.js와 socket.io.js를 포함.
- top.html : 상단화면을 구성, system.html을 포함.
- system.html : 라즈베리파이 cpu온도, cpu사용률, 메모리, 디스크 사용률을 실시간 표시.
- menu.html : sub 메뉴를 표시.
- top.html : 상단화면을 구성, system.html을 포함.
- 폴더 구조
- html : html 파일 집합 폴더
- common : html에서 공통적으로 사용하는 html 파일
- static : resource 폴더, js, css, image 등이 포함
- user : 사용자 정의 프로그램(js, css)
- js : 사용자 정의 js 프로그램이 위치함
- user : 사용자 정의 프로그램(js, css)
- html : html 파일 집합 폴더
- static/user/js/system_info.js
- sokect.io 통신을 통해 라즈베리파이의 전반적인 system 상태를 모니터링 함
$(document).ready(function() {
var cpu_obj = $("#cpuChart")[0].getContext('2d');
var cpu_chart = new Chart(cpu_obj, {
type: 'bar',
data: {
labels: ['CPU 사용률'],
datasets: [{
label: 'CPU 사용률',
data: [0],
backgroundColor: 'rgba(75, 192, 192, 0.2)'
}]
},
options: {
indexAxis: 'y',
scales: {
x: {
beginAtZero: true,
max: 100,
ticks: {
stepSize: 10,
color: 'white'
}
},
y: {
ticks: {
display: false
}
}
},
plugins: {
legend: {
display: false
},
title: {
display: false,
text: 'CPU 사용률',
color: 'white',
font: {
size: 16
}
}
}
}
});
var memory_obj = $("#memoryChart")[0].getContext('2d');
var memory_chart = new Chart(memory_obj, {
type: 'bar',
data: {
labels: ['메모리 사용률'],
datasets: [{
label: '메모리 사용률',
data: [0],
backgroundColor: 'rgba(75, 192, 192, 0.2)'
}]
},
options: {
indexAxis: 'y',
scales: {
x: {
beginAtZero: true,
max: 100,
ticks: {
stepSize: 10,
color: 'white', // 눈금자 글자색을 하얀색으로
}
},
y: {
ticks: {
display: false // 왼쪽 축 레이블 숨기기
}
}
},
plugins: {
legend: {
display: false
},
title: {
display: false,
text: '메모리 사용률',
color: 'white', // 레이블 글자색을 하얀색으로
font: {
size: 16 // 글자 크기 키우기
}
}
}
}
});
var disk_obj = $("#diskChart")[0].getContext('2d');
var disk_chart = new Chart(disk_obj, {
type: 'bar',
data: {
labels: ['디스크 사용률'],
datasets: [{
label: '디스크 사용률',
data: [0],
backgroundColor: 'rgba(75, 192, 192, 0.2)'
}]
},
options: {
indexAxis: 'y',
scales: {
x: {
beginAtZero: true,
max: 100,
ticks: {
stepSize: 10,
color: 'white', // 눈금자 글자색을 하얀색으로
}
},
y: {
ticks: {
display: false // 왼쪽 축 레이블 숨기기
}
}
},
plugins: {
legend: {
display: false
},
title: {
display: false,
text: '디스크 사용률',
color: 'white', // 레이블 글자색을 하얀색으로
font: {
size: 16 // 글자 크기 키우기
}
}
}
}
});
var currentTemperature = 0; // 현재 온도를 저장할 전역 변수
var temp_gage = "";
// 창 크기 변경 시 게이지 재생성 함수
function resizeGauge() {
// 기존 게이지 인스턴스 제거
if(temp_gage != ""){
temp_gage.destroy();
}
// 새 게이지 생성
temp_gage = new JustGage({
id: 'cpuTemp',
value: currentTemperature,
min: 0,
max: 150,
decimals: 1,
title: "CPU 온도",
label: "CPU Temperature ",
gaugeWidthScale: 0.6,
relativeGaugeSize: true,
symbol: '°C',
pointer: true,
pointerOptions: {
toplength: -15,
bottomlength: 10,
bottomwidth: 8,
color: '#8e8e93',
stroke: '#ffffff',
stroke_width: 2,
stroke_linecap: 'round'
},
titleMinFontSize: 20, // 제목 폰트 크기 설정
valueMinFontSize: 20, // 값 폰트 크기 설정
labelMinFontSize: 12, // 라벨 폰트 크기 설정
minLabelMinFontSize: 14, // 최소값 라벨 폰트 크기 설정
maxLabelMinFontSize: 14, // 최대값 라벨 폰트 크기 설정
titleFontColor: "#fff", // 제목 폰트 색상 설정
valueFontColor: "red", // 값 폰트 색상 설정
labelFontColor: "#fff", // 라벨 폰트 색상 설정
minLabelFontColor: "#fff", // 최소값 라벨 폰트 색상 설정
maxLabelFontColor: "#fff" // 최대값 라벨 폰트 색상 설정
});
}
// 창 크기 변경 시 게이지 재생성 이벤트 리스너 등록
window.addEventListener('resize', resizeGauge);
resizeGauge();
function getValueToColor(value){
var color;
if (value <= 50) {
color = '#00FF00';
} else if (color <= 80) {
color = '#FFFF00';
} else {
color = '#FF0000';
}
return color;
}
// 온도를 업데이트하는 함수
function updateTemperature(newTemp) {
currentTemperature = newTemp; // 전역 변수에 현재 온도 저장
temp_gage.refresh(newTemp); // 게이지 값 업데이트
//console.log("온도,", currentTemperature)
// 온도에 따라 색상 변경
var color;
color = getValueToColor(currentTemperature)
temp_gage.update({ valueFontColor: color });
}
var avgCpuSum = 0;
var avgCpuCnt = 0;
var cpuCheckCnt = 0;
function cpuUsed(num) {
cpuCheckCnt++;
if (cpuCheckCnt < 2) {
//console.log("평균");
return false;
}
var cpuUsage = parseFloat(num).toFixed(1);
var color;
avgCpuCnt++;
avgCpuSum += Number(cpuUsage);
var avgCpu = avgCpuSum / avgCpuCnt;
//console.log("평균cpu사용률,",avgCpuCnt+","+cpuUsage+","+parseFloat(avgCpu).toFixed(1));
color = getValueToColor(cpuUsage)
$(".chart-cpu-value").html(cpuUsage + "%").css('color', color);
cpu_chart.data.datasets[0].backgroundColor = color;
cpu_chart.data.datasets[0].data[0] = cpuUsage;
cpu_chart.update();
}
function memoryUsed(num, data) {
var memoryUsage = num;
var color;
color = getValueToColor(memoryUsage)
var total_memory_bytes = data.total_memory;
var used_memory_bytes = data.used_memory;
var total_memory_gb = total_memory_bytes / Math.pow(1024, 3);
var used_memory_gb = used_memory_bytes / Math.pow(1024, 3);
//console.log("Total Memory: " + total_memory_gb.toFixed(2) + " GB");
//console.log("Used Memory: " + used_memory_gb.toFixed(2) + " GB");
$(".chart-memory-value").html(used_memory_gb.toFixed(2) + " GB" + " / " + total_memory_gb.toFixed(2) + " GB").css('color', color);
memory_chart.data.datasets[0].backgroundColor = color;
memory_chart.data.datasets[0].data[0] = memoryUsage;
memory_chart.update();
}
function diskUsed(num, data) {
var diskUsage = num;
var color;
color = getValueToColor(diskUsage)
var total_disk_bytes = data.total_disk;
var used_disk_bytes = data.used_disk;
var total_disk_gb = total_disk_bytes / Math.pow(1024, 3);
var used_disk_gb = used_disk_bytes / Math.pow(1024, 3);
$(".chart-disk-value").html(used_disk_gb.toFixed(2) + " GB" + " / " + total_disk_gb.toFixed(2) + " GB").css('color', color);
disk_chart.data.datasets[0].backgroundColor = color;
disk_chart.data.datasets[0].data[0] = diskUsage;
disk_chart.update();
}
setInterval(function() {
// 최초 데이터 요청
socket.emit('get_system_info', {});
}, 1000);
socket.on('ret_system_info', function(data) {
//console.log("시스템 상태",data)
cpuUsed(data.cpu_percent);
memoryUsed(data.memory_percent, data);
diskUsed(data.disk_percent, data);
updateTemperature(data.cpu_temp);
});
});
※ 현재(가상환경)에서 설치된 파이썬 라이브러리 목록 가져오고 새로운 환경에서 설치하기
- https://hull.kr/IoT/17 필수 라이브러리 참조
#리스트
pip freeze > install_lib.txt
#설치
pip install -r install_lib.txt
댓글목록0