บทที่ 8 การพัฒนา Dashboard ด้วย JavaScript

7.1 บทนำ

ในบทนี้ จะนำความรู้ JavaScript ที่ได้ศึกษามาแล้ว มาประยุกต์ใช้ในการพัฒนา Dashboard สำหรับระบบ IoT โดยให้ JavaScript ทำหน้าที่ติดต่อกับ Server ผ่าน HTTP/HTTPS และรับข้อมูลในรูปแบบ JSON จาก API เพื่อนำมาแสดงผลบนหน้าเว็บ

ภาพรวมของการทำงานสามารถแสดงได้ดังนี้

โดย JavaScript สามารถเปลี่ยนแหล่งข้อมูลได้ หรือสามารถดึงข้อมูลมาจากแหล่งข้อมูลอื่น ๆ ได้

8.2 แนวคิดของ Dashboard

Dashboard คือหน้าจอที่นำข้อมูลจำนวนมากมาแสดงในรูปแบบที่ผู้ใช้งานสามารถเข้าใจได้อย่างรวดเร็ว สำหรับระบบ IoT สามารถแสดง

ตัวอย่าง

JavaScript ที่ทำงานอยู่บน Browser ไม่ได้ติดต่อ PostgreSQL โดยตรง แต่ติดต่อผ่าน API

GET http://127.0.0.1:8000/device

Server อาจตอบกลับ

JavaScript จะนำข้อมูลดังกล่าวไปสร้าง Dashboard

การใช้ Fetch API

fetch() เป็น API ของ JavaScript สำหรับสร้าง HTTP Request ตัวอย่างการอ่านข้อมูล fetch("http://127.0.0.1:8000/device") สามารถใช้กับ HTTP Method ต่าง ๆ เช่น GET ,POST, PUT , DELETE ดังนั้นความรู้จากบทนี้สามารถนำไปใช้กับระบบ Web และ IoT ที่ใช้ REST API ได้

เมื่อ Server ส่ง JSON กลับมา จะต้องแปลง JSON เป็น JavaScript Object เช่นมีการส่งข้อมูล JSON

JavaScript สามารถแปลงข้อมูลด้วยคำสั่ง const data = await response.json(); จากนั้นสามารถเข้าถึงข้อมูลได้

สำหรับข้อมูลหลายรายการ

การสร้าง Dashboard ด้วย HTML และ CSS นั้น HTML ทำหน้าที่กำหนดโครงสร้าง และ CSS ทำหน้าที่จัดรูปแบบ เช่น

- ขนาด

- ตำแหน่ง

- ระยะห่าง

- ตาราง

- Card

JavaScript สามารถนำข้อมูลจาก API มาเปลี่ยนแปลงเป็น HTML เพือแสดงผลบนหน้าจอได้

การดึงข้อมูลแบบอัตโนมัติ

หลายงานที่ข้อมูลมีการเปลี่ยนแปลงตลอดเวลา จึงสามารถกำหนดให้ JavaScript เรียก API เป็นระยะได้ เช่น เช่น ทุก 5 วินาทีให้ดึงหรือขอข้อมูล API จากเซิฟเวอร์ด้วยคำสั่ง setInterval(loadData, 5000);

ตัวอย่างโปรแกรมแสดงค่าอุณภูมิและความชื้นล่าสุดของ ESP32

ให้นำโค้ดตัวอย่างการดึงค่าอุณหภูมิล่าสุดมาแสดงบน dashboard ไปสร้างเป็นไฟล์ dashboard.html


      <!DOCTYPE html>
<html lang="th">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IoT Sensor Dashboard</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background: #f2f2f2;
            text-align: center;
            margin: 0;
            padding: 30px;
        }
        h1 {
            margin-bottom: 30px;
        }
        .dashboard {
            display: flex;
            justify-content: center;
            gap: 30px;
            flex-wrap: wrap;
        }
        .card {
            background: white;
            width: 250px;
            padding: 30px;
            border-radius: 15px;
            box-shadow: 0 3px 10px rgba(0,0,0,0.15);
        }
        .title {
            font-size: 20px;
            margin-bottom: 15px;
        }
        .value {
            font-size: 42px;
            font-weight: bold;
        }
        .unit {
            font-size: 20px;
        }
        #device {
            margin-top: 30px;
            font-size: 18px;
        }
        #time {
            margin-top: 10px;
            color: gray;
        }
        #status {
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <h1>IoT Sensor Dashboard</h1>

    <div class="dashboard">
        <!-- Temperature -->
        <div class="card">
            <div class="title">Temperature</div>
            <div class="value">
                <span id="temperature">--</span>
                <span class="unit">°C</span>
            </div>
        </div>

        <!-- Humidity -->
        <div class="card">
            <div class="title">Humidity</div>
            <div class="value">
                <span id="humidity">--</span>
                <span class="unit">%</span>
            </div>
        </div>
    </div>

    <div id="device">Device: --</div>
    <div id="time">Last Update: --</div>
    <div id="status">กำลังเชื่อมต่อ Server...</div>

    <script>
        // ==========================================
        // อ่านข้อมูล Sensor ล่าสุด
        // ==========================================
        async function loadSensorData() {
            try {
                const response = await fetch("http://127.0.0.1:8000/sensor/latest");

                // ตรวจสอบ HTTP Status
                if (!response.ok) {
                    throw new Error("HTTP Error: " + response.status);
                }

                // แปลง JSON เป็น JavaScript Object
                const data = await response.json();

                // ==================================
                // นำข้อมูลมาแสดงบนหน้าเว็บ
                // ==================================
                document.getElementById("temperature").textContent = data.temperature;
                document.getElementById("humidity").textContent = data.humidity;
                document.getElementById("device").textContent = "Device: " + data.device_id;
                document.getElementById("time").textContent = "Last Update: " + data.timestamp;
                document.getElementById("status").textContent = "เชื่อมต่อ Server สำเร็จ";
            }
            catch (error) {
                console.error(error);
                document.getElementById("status").textContent = "ไม่สามารถเชื่อมต่อ Server ได้";
            }
        }

        // ==========================================
        // เรียกข้อมูลครั้งแรก
        // ==========================================
        loadSensorData();

        // ==========================================
        // อ่านข้อมูลใหม่ทุก 5 วินาที
        // ==========================================
        setInterval(loadSensorData, 5000);
    
</body>
</html>

    

ในส่วนของด้านเซิฟเวอร์ให้สร้างไฟล์ server_fastAPI_dashboard.py โดยใช้โค้ดดังในตัวอย่างด้านล่างนี้


 from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import psycopg

# -------------------------
# FastAPI
# -------------------------

app = FastAPI()

# -------------------------
# CORS
# -------------------------

app.add_middleware(

    CORSMiddleware,

    allow_origins=["*"],

    allow_credentials=True,

    allow_methods=["*"],

    allow_headers=["*"]

)

# -------------------------
# PostgreSQL
# -------------------------

conn = psycopg.connect(

    host="localhost",
    dbname="iot_database",
    user="postgres",
    password="***********",
    port=5432

)

print("เชื่อมต่อ PostgreSQL สำเร็จ")

# -------------------------
# JSON Model
# -------------------------

class Device(BaseModel):

    device_id: str
    device_name: str
    location: str
    ip_address: str


# ==========================================================
# POST : เพิ่มข้อมูลอุปกรณ์
# ==========================================================

@app.post("/device")
def add_device(device: Device):

    try:

        with conn.cursor() as cur:

            cur.execute(
                """
                INSERT INTO device
                (
                    device_id,
                    device_name,
                    location,
                    ip_address
                )

                VALUES
                (%s,%s,%s,%s)
                """,

                (
                    device.device_id,
                    device.device_name,
                    device.location,
                    device.ip_address
                )

            )

        conn.commit()

        print("บันทึกข้อมูลสำเร็จ")

        return {

            "status":"OK",
            "message":"บันทึกข้อมูลสำเร็จ"

        }

    except Exception as e:

        conn.rollback()

        print(e)

        return {

            "status":"ERROR",
            "message":str(e)

        }


# ==========================================================
# GET : อ่านข้อมูลทั้งหมด
# ==========================================================

@app.get("/device")
def get_device():

    try:

        with conn.cursor() as cur:

            cur.execute(
                """
                SELECT

                    device_id,
                    device_name,
                    location,
                    ip_address

                FROM device

                ORDER BY device_id
                """
            )

            rows = cur.fetchall()

        device_list = []

        for row in rows:

            device_list.append({

                "device_id": row[0],
                "device_name": row[1],
                "location": row[2],
                "ip_address": row[3]

            })

        return device_list

    except Exception as e:

        print(e)

        return {

            "status":"ERROR",
            "message":str(e)

        }


# ==========================================================
# GET : อ่านค่า Temperature และ Humidity ล่าสุด
# ==========================================================

@app.get("/sensor/latest")
def get_latest_sensor():

    try:

        with conn.cursor() as cur:

            cur.execute(
                """
                SELECT
                    data_id,
                    device_id,
                    temperature,
                    humidity,
                    recorded_at
                FROM sensor_data
                ORDER BY recorded_at DESC
                LIMIT 1
                """
            )

            row = cur.fetchone()

        if row is None:

            return {
                "status": "ERROR",
                "message": "ไม่พบข้อมูล Sensor"
            }

        return {

            "data_id": row[0],

            "device_id": row[1],

            "temperature": float(row[2]),

            "humidity": float(row[3]),

            "recorded_at": row[4]

        }

    except Exception as e:

        print("Database Error:", e)

        return {

            "status": "ERROR",

            "message": str(e)

        }

ทำการรันไฟล์ server_fastAPI_dashboard บนเซอร์เวอร์ ด้วยคำสั่ง uvicorn server_fastAPI_dashboard:app --reload

จะปรากฏหน้าต่างบนเซอร์เวอร์ดังรูป

จากนั้นเปิดไฟล์โปรแกรมฝั่ง Client ชื่อไฟล์ dashboard.html จะเห็นค่าของอุณหภูมิและความชื้นล่าสุดของตารางจากฐานข้อมูลแสดงบนหน้าต่างดังรูป

และฝั่ง Server จะปรากฏการแสดงการร้องขอมาจาก client และการสื่อสารที่สมบูรณ์ ดังแสดงในรูป

ซึ่งหากเปิดตาราง sensor_data ของฐานข้อมูล ก็จะเห็นค่าข้อมูลล่าสุดในตารางดังรูป

ให้นำ ESP32 มาประยุกต์ใช้ในการพัฒนาให้อ่านค่าอุณหภูมิและความชื้น ส่งค่ามาเก็บไว้ในตาราง sensor_data ทุก ๆ 5 วินาที เพื่อใช้สำหรับการนำไปใช้แสดงกราฟในการทดลองหัวข้อต่อไป โดยอ้างอิงงานมอบหมายในบทที่ 5

ให้สร้าง dashboard เพื่อแสดงค่าข้อมูลล่าสุดของ ESP32-02 ตัวที่ 2 ดังลักษณะในรูป

หัวข้อ