2026年3月23日 星期一

ESP32 → HTTP → Python API → TimescaleDB

安裝PostgreSQL 15(不選擇太新的版本是因為怕不支援timescaledb)

先確認是否有timescaledb [SELECT * FROM pg_extension;]

沒有的話 先安裝 TimescaleDB:

一開始遇到問題(TimescaleDB installer 找不到 PostgreSQL pg_config )



所以我把載檔移到了 這個路徑下

C:\Users\Hon\Downloads\timescaledb-postgresql-15-windows-amd64\timescaledb



再去執行setup.exe

有問題都按y


PostgreSQL執行:

CREATE EXTENSION timescaledb;

SELECT * FROM pg_extension;


VSCode Terminal 



python api_server.py

from flask import Flask, request, jsonify, render_template

import psycopg2


app = Flask(__name__)


conn = psycopg2.connect(

    host="localhost",

    database="postgres",

    user="postgres",

    password="1234"

)


@app.route("/")

def index():

    return render_template("index.html")


@app.route("/data", methods=["GET"])

def get_data():


    cur = conn.cursor()


    cur.execute("""

        SELECT time, temperature

        FROM sensor_data

        ORDER BY time DESC

        LIMIT 50

    """)


    rows = cur.fetchall()


    data = []

    for row in rows:

        data.append({

            "time": row[0].strftime("%H:%M:%S"),

            "temp": row[1]

        })


    cur.close()


    response = jsonify(data[::-1])

    response.headers["Cache-Control"] = "no-cache"

    return response


@app.route("/sensor", methods=["POST"])

def receive_data():


    data = request.json


    device = data.get("device", "unknown")

    temp = data.get("temp", 0)


    cur = conn.cursor()


    cur.execute(

        """

        INSERT INTO sensor_data(time, device_id, temperature)

        VALUES (NOW(), %s, %s)

        """,

        (device, temp)

    )


    conn.commit()

    cur.close()


    print("Inserted:", device, temp)


    return jsonify({"status": "ok"})


if __name__ == "__main__":

    app.run(host="0.0.0.0", port=5000, debug=True)




arduino程式:

#include <SPI.h>

#include <Ethernet2.h>

#include <ModbusMaster.h>


// ===== W5500 =====

#define W5500_CS 6

#define W5500_RST 14


byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

IPAddress server(192,168,0,220); // 你的電腦IP


EthernetClient client;


// ===== RS485 =====

#define RS_TX_PIN 5

#define RS_RX_PIN 4

#define RS_ENAMBLE_232_PIN 21

#define RS_ENAMBLE_422_PIN 16

#define RS_ENAMBLE_485_PIN 15


#define RS_BAUDRATE 9600


ModbusMaster node;


// ===== Modbus callback =====

void preTransmission() {

while (Serial2.available()) Serial2.read();

}


void postTransmission() {

Serial2.flush();

delayMicroseconds(100);

while (Serial2.available()) Serial2.read();

}


// ===== setup =====

void setup() {

Serial.begin(115200);


// ===== W5500 reset =====

pinMode(W5500_RST, OUTPUT);

digitalWrite(W5500_RST, LOW);

delay(20);

digitalWrite(W5500_RST, HIGH);

delay(100);


// ⚠️ 用你成功的 SPI 腳位

SPI.begin(12,13,11,W5500_CS);

Ethernet.init(W5500_CS);


IPAddress ip(192,168,0,50);

IPAddress gateway(192,168,0,1);

IPAddress subnet(255,255,255,0);


Ethernet.begin(mac, ip, gateway, gateway, subnet);


Serial.print("IP: ");

Serial.println(Ethernet.localIP());


// ===== RS485 =====

pinMode(RS_ENAMBLE_232_PIN, OUTPUT);

pinMode(RS_ENAMBLE_422_PIN, OUTPUT);

pinMode(RS_ENAMBLE_485_PIN, OUTPUT);


digitalWrite(RS_ENAMBLE_232_PIN, LOW);

digitalWrite(RS_ENAMBLE_422_PIN, LOW);

digitalWrite(RS_ENAMBLE_485_PIN, HIGH);


Serial2.begin(RS_BAUDRATE, SERIAL_8N1, RS_RX_PIN, RS_TX_PIN);


node.begin(1, Serial2);

node.preTransmission(preTransmission);

node.postTransmission(postTransmission);


Serial.println("System Ready");

}


// ===== loop =====

void loop() {


uint8_t result;

uint16_t raw;


// ✅ 用你成功的地址

result = node.readHoldingRegisters(43, 1);


if (result == node.ku8MBSuccess) {


raw = node.getResponseBuffer(0);


float temperature = raw / 10.0;


Serial.print("Temp: ");

Serial.println(temperature);


// ===== HTTP 傳送 =====

if (client.connect(server, 5000)) {


String json =

"{\"device\":\"CH4_SENSOR\",\"temp\":" + String(temperature) + "}";


client.println("POST /sensor HTTP/1.1");

client.println("Host: 192.168.0.220");

client.println("Content-Type: application/json");

client.print("Content-Length: ");

client.println(json.length());

client.println();

client.println(json);


Serial.println("Data sent!");


client.stop();

} else {

Serial.println("HTTP failed");

}


} else {

Serial.print("Modbus error: ");

Serial.println(result);

}


delay(2000);

}



建立檔案: templates/index.html

你的專案/

├── api_server.py

└── templates/

        └── index.html


打開: http://localhost:5000 (同個網域的都可以連線網站)

index.html:

<!DOCTYPE html>

<html>

<head>

  <title>Factory Dashboard</title>

  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

</head>


<body style="background:#111;color:white;font-family:sans-serif">


<h1>? 工廠監控畫面</h1>


<h2>溫度: <span id="temp">--</span> °C</h2>


<canvas id="chart" width="600" height="300"></canvas>


<script>


const ctx = document.getElementById('chart').getContext('2d');


let chart = new Chart(ctx, {

    type: 'line',

    data: {

        labels: [],

        datasets: [{

            label: 'Temperature',

            data: [],

            borderColor: 'lime',

            fill: false

        }]

    },

    options: {

        scales: {

            y: {

                ticks: { color: "white" }

            },

            x: {

                ticks: { color: "white" }

            }

        },

        plugins: {

            legend: {

                labels: { color: "white" }

            }

        }

    }

});


function updateData() {

    fetch('/data')

    .then(res => res.json())

    .then(data => {


        let labels = [];

        let temps = [];


        data.forEach(d => {

            labels.push(d.time);

            temps.push(d.temp);

        });


        chart.data.labels = labels;

        chart.data.datasets[0].data = temps;

        chart.update();


        // 顯示最新溫度

        let latest = temps[temps.length - 1];

        document.getElementById("temp").innerText = latest;


        // 警報(> 50°C

        if (latest > 50) {

            document.body.style.background = "red";

        } else {

            document.body.style.background = "#111";

        }

    });

}


// 2秒更新

setInterval(updateData, 2000);


</script>


</body>

</html>


網頁畫面:





沒有留言:

張貼留言

STM32

  STM32 硬體腳位接線 : Keil 程式燒入操作 : 前置設定 開啟 Keil 工程檔案後,進入 Options for Target 設定頁面 。 在 Debug 分頁選取 ST-Link Debugger 作為燒錄器 。 點擊工具列的...