IoT 웹 사용자 인터페이스 > IoT 사물인터넷 유무선제어

본문 바로가기

[실습] IoT 웹 사용자 인터페이스

필기자
2024-04-01 13:20 258 0
  • - 첨부파일 : iot.zip (3.5M) - 다운로드

본문

IoT 웹 사용자 인터페이스
※ 첨부 파일은 html과 static만 포함함. python은 아래 코드로 생성하여야 함.

목 적
  • 라즈베리파이에서 엑츄레이터 및 센서 제어를 위한 user interface를 작성한다.
  • 라즈베리파이에서  http,  socket.io 통신을 활용하여 웹 서비스를 제공한다.
목 차
1. 웹 서비스를 위한 파이썬 프로그램 개발
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 메뉴를 표시.
20240401151344_abe77cd4145dbe7a0bebce7efd43333a_wya1.png
  • 폴더 구조
    • html : html 파일 집합 폴더
      • common : html에서 공통적으로 사용하는 html 파일
    • static : resource 폴더, js, css, image 등이 포함
      • user : 사용자 정의 프로그램(js, css)
        • js : 사용자 정의 js 프로그램이 위치함
20240401152542_abe77cd4145dbe7a0bebce7efd43333a_ndob.png
  • 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);
    });
});



※ 현재(가상환경)에서 설치된 파이썬 라이브러리 목록 가져오고 새로운 환경에서 설치하기

#리스트 
pip freeze > install_lib.txt

#설치
pip install -r install_lib.txt

댓글목록0

등록된 댓글이 없습니다.
게시판 전체검색