460 lines
13 KiB
C
460 lines
13 KiB
C
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "bme280.h" // Deine BME280 Sensor-Bibliothek
|
||
|
#include "hardware/i2c.h" // Nötig für I2C-Definitionen, falls bme280.h sie nicht enthält
|
||
|
#include "lwip/pbuf.h"
|
||
|
#include "lwip/tcp.h"
|
||
|
#include "pico/cyw43_arch.h"
|
||
|
#include "pico/stdlib.h"
|
||
|
|
||
|
#define TCP_PORT 80
|
||
|
#define DEBUG_printf printf
|
||
|
#define BUF_SIZE 8192 * 4 // Kann angepasst werden für größere HTML/JSON
|
||
|
#define POLL_TIME_S 5
|
||
|
|
||
|
static uint64_t __tcp_errors = 0;
|
||
|
|
||
|
// Deklariere den BME280 Kontext als externe globale Variable.
|
||
|
// Dieser wird in main.cpp initialisiert und hier verwendet.
|
||
|
extern bme280_ctx *ctx; // WICHTIG: Nutzt jetzt denselben Namen wie in main.cpp
|
||
|
|
||
|
struct data {
|
||
|
float t;
|
||
|
float h;
|
||
|
};
|
||
|
|
||
|
extern struct data _list[1000];
|
||
|
extern int list_index;
|
||
|
extern int list_count;
|
||
|
|
||
|
const char *html_page = R"rawliteral(
|
||
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="UTF-8" />
|
||
|
<title>Dashboard oder so</title>
|
||
|
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&display=swap" rel="stylesheet">
|
||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||
|
<style>
|
||
|
body {
|
||
|
background: #f4f6f8;
|
||
|
font-family: 'Montserrat', sans-serif;
|
||
|
padding: 20px;
|
||
|
color: #333;
|
||
|
}
|
||
|
|
||
|
h1, h2 {
|
||
|
font-weight: 600;
|
||
|
text-align: center;
|
||
|
}
|
||
|
|
||
|
.chart-container {
|
||
|
width: 100%;
|
||
|
max-width: 800px;
|
||
|
margin: 20px auto;
|
||
|
background: #fff;
|
||
|
padding: 20px;
|
||
|
border-radius: 12px;
|
||
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||
|
}
|
||
|
|
||
|
.live-data {
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
gap: 30px;
|
||
|
font-size: 1.5em;
|
||
|
margin-top: 10px;
|
||
|
}
|
||
|
|
||
|
canvas {
|
||
|
width: 100% !important;
|
||
|
height: 300px !important;
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<h1>Dashboard oder so</h1>
|
||
|
|
||
|
<div class="chart-container live-data">
|
||
|
<div>🌡 Temp: <span id="liveTemp">--</span> °C</div>
|
||
|
<div>💧 Humidity: <span id="liveHumidity">--</span> %</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="chart-container">
|
||
|
<h2>Temperature History</h2>
|
||
|
<canvas id="tempChart"></canvas>
|
||
|
</div>
|
||
|
|
||
|
<div class="chart-container">
|
||
|
<h2>Humidity History</h2>
|
||
|
<canvas id="humChart"></canvas>
|
||
|
</div>
|
||
|
|
||
|
<script>
|
||
|
let tempChart, humChart;
|
||
|
|
||
|
function initCharts() {
|
||
|
const tempCtx = document.getElementById('tempChart').getContext('2d');
|
||
|
const humCtx = document.getElementById('humChart').getContext('2d');
|
||
|
|
||
|
const options = {
|
||
|
type: 'line',
|
||
|
options: {
|
||
|
responsive: true,
|
||
|
animation: false,
|
||
|
scales: {
|
||
|
x: {
|
||
|
title: { display: true, text: 'Time' },
|
||
|
ticks: { autoSkip: true, maxTicksLimit: 10 }
|
||
|
},
|
||
|
y: { beginAtZero: false }
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
tempChart = new Chart(tempCtx, {
|
||
|
...options,
|
||
|
data: {
|
||
|
labels: [],
|
||
|
datasets: [{
|
||
|
label: 'Temperature (°C)',
|
||
|
data: [],
|
||
|
borderColor: '#e74c3c',
|
||
|
backgroundColor: 'rgba(231, 76, 60, 0.2)',
|
||
|
tension: 0.3,
|
||
|
fill: true,
|
||
|
pointRadius: 2
|
||
|
}]
|
||
|
}
|
||
|
});
|
||
|
|
||
|
humChart = new Chart(humCtx, {
|
||
|
...options,
|
||
|
data: {
|
||
|
labels: [],
|
||
|
datasets: [{
|
||
|
label: 'Humidity (%)',
|
||
|
data: [],
|
||
|
borderColor: '#3498db',
|
||
|
backgroundColor: 'rgba(52, 152, 219, 0.2)',
|
||
|
tension: 0.3,
|
||
|
fill: true,
|
||
|
pointRadius: 2
|
||
|
}]
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function updateLiveData() {
|
||
|
fetch('/data')
|
||
|
.then(res => res.json())
|
||
|
.then(data => {
|
||
|
document.getElementById('liveTemp').textContent = data.temp.toFixed(1);
|
||
|
document.getElementById('liveHumidity').textContent = data.humidity.toFixed(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function updateCharts() {
|
||
|
fetch('/history')
|
||
|
.then(res => res.json())
|
||
|
.then(data => {
|
||
|
const labels = data.map((_, i) => {
|
||
|
const t = new Date(Date.now() - (data.length - 1 - i) * 10000);
|
||
|
return t.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||
|
});
|
||
|
|
||
|
const temps = data.map(e => e.t);
|
||
|
const hums = data.map(e => e.h);
|
||
|
|
||
|
tempChart.data.labels = labels;
|
||
|
tempChart.data.datasets[0].data = temps;
|
||
|
tempChart.update();
|
||
|
|
||
|
humChart.data.labels = labels;
|
||
|
humChart.data.datasets[0].data = hums;
|
||
|
humChart.update();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
initCharts();
|
||
|
updateCharts();
|
||
|
updateLiveData();
|
||
|
setInterval(updateCharts, 10000); // alle 10s Verlaufsdaten
|
||
|
setInterval(updateLiveData, 2000); // alle 2s Livewerte
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|
||
|
)rawliteral";
|
||
|
|
||
|
typedef struct HTTP_SERVER_T_ {
|
||
|
struct tcp_pcb *server_pcb;
|
||
|
struct tcp_pcb *client_pcb;
|
||
|
} HTTP_SERVER_T;
|
||
|
|
||
|
static HTTP_SERVER_T *http_server_init(void) {
|
||
|
HTTP_SERVER_T *state = (HTTP_SERVER_T *)calloc(1, sizeof(HTTP_SERVER_T));
|
||
|
if (!state) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to allocate HTTP server state\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
static err_t http_server_close(void *arg) {
|
||
|
HTTP_SERVER_T *state = (HTTP_SERVER_T *)arg;
|
||
|
err_t err = ERR_OK;
|
||
|
if (state->client_pcb != NULL) {
|
||
|
tcp_arg(state->client_pcb, NULL);
|
||
|
tcp_poll(state->client_pcb, NULL, 0);
|
||
|
tcp_sent(state->client_pcb, NULL);
|
||
|
tcp_recv(state->client_pcb, NULL);
|
||
|
tcp_err(state->client_pcb, NULL);
|
||
|
err = tcp_close(state->client_pcb);
|
||
|
if (err != ERR_OK) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Close failed %d, calling abort\n", err);
|
||
|
tcp_abort(state->client_pcb);
|
||
|
err = ERR_ABRT;
|
||
|
}
|
||
|
state->client_pcb = NULL;
|
||
|
}
|
||
|
// Keep this off to keep the server alive
|
||
|
// if (state->server_pcb) {
|
||
|
// tcp_arg(state->server_pcb, NULL);
|
||
|
// tcp_close(state->server_pcb);
|
||
|
// state->server_pcb = NULL;
|
||
|
//}
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static err_t http_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) {
|
||
|
DEBUG_printf("HTTP data sent, length: %u\n", len);
|
||
|
return http_server_close(arg); // Verbindung nach dem Senden schließen
|
||
|
}
|
||
|
|
||
|
static err_t http_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p,
|
||
|
err_t err) {
|
||
|
HTTP_SERVER_T *state = (HTTP_SERVER_T *)arg;
|
||
|
|
||
|
if (!p) {
|
||
|
// Client hat Verbindung geschlossen
|
||
|
return http_server_close(arg);
|
||
|
}
|
||
|
if (err != ERR_OK) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Error in TCP receive: %d\n", err);
|
||
|
pbuf_free(p);
|
||
|
return http_server_close(arg);
|
||
|
}
|
||
|
|
||
|
cyw43_arch_lwip_check();
|
||
|
|
||
|
// Eingehende Anfrage verarbeiten
|
||
|
if (p->tot_len > 0) {
|
||
|
char *req = (char *)malloc(p->tot_len + 1);
|
||
|
if (req == NULL) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to allocate memory for request\n");
|
||
|
pbuf_free(p);
|
||
|
return http_server_close(arg);
|
||
|
}
|
||
|
pbuf_copy_partial(p, req, p->tot_len, 0);
|
||
|
req[p->tot_len] = '\0'; // Request-String nullterminieren
|
||
|
|
||
|
// DEBUG_printf("Received HTTP request:\n%s\n", req);
|
||
|
|
||
|
// Einfaches Routing basierend auf der URL
|
||
|
const char *http_header_ok_html =
|
||
|
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: "
|
||
|
"%d\r\nConnection: close\r\n\r\n";
|
||
|
const char *http_header_ok_json =
|
||
|
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: "
|
||
|
"%d\r\nConnection: close\r\n\r\n";
|
||
|
const char *http_header_not_found =
|
||
|
"HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: "
|
||
|
"13\r\nConnection: close\r\n\r\n";
|
||
|
const char *not_found_body = "404 Not Found";
|
||
|
|
||
|
char response_header[256];
|
||
|
const char *response_body;
|
||
|
size_t response_body_len;
|
||
|
char dynamic_json_body[256]; // Puffer für dynamische JSON-Daten
|
||
|
char *history_body = 0;
|
||
|
|
||
|
if (strstr(req, "GET / HTTP/1.1") ||
|
||
|
strstr(req, "GET /index.html HTTP/1.1")) {
|
||
|
response_body = html_page;
|
||
|
response_body_len = strlen(html_page);
|
||
|
sprintf(response_header, http_header_ok_html, response_body_len);
|
||
|
} else if (strstr(req, "GET /data HTTP/1.1")) {
|
||
|
// Sensordaten auslesen
|
||
|
float temp = 0.0;
|
||
|
float humidity = 0.0;
|
||
|
if (ctx != NULL) { // Zugriff auf die globale ctx Variable
|
||
|
ctx->update(ctx); // Sensorwerte aktualisieren
|
||
|
temp = ctx->read_temp(ctx);
|
||
|
humidity = ctx->read_humidity(ctx);
|
||
|
} else {
|
||
|
DEBUG_printf("BME280 Kontext ist NULL, sende Standarddaten.\n");
|
||
|
}
|
||
|
sprintf(dynamic_json_body, "{\"temp\": %.2f, \"humidity\": %.2f}", temp,
|
||
|
humidity);
|
||
|
|
||
|
response_body = dynamic_json_body;
|
||
|
response_body_len = strlen(dynamic_json_body);
|
||
|
sprintf(response_header, http_header_ok_json, response_body_len);
|
||
|
} else if (strstr(req, "GET /history HTTP/1.1")) {
|
||
|
history_body =
|
||
|
(char *)malloc(4096); // or larger, depending on expected size
|
||
|
if (!history_body) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Memory alloc failed for history\n");
|
||
|
free(req);
|
||
|
pbuf_free(p);
|
||
|
return http_server_close(arg);
|
||
|
}
|
||
|
|
||
|
char *ptr = history_body;
|
||
|
ptr += sprintf(ptr, "[");
|
||
|
|
||
|
for (int i = 0; i < list_count; i++) {
|
||
|
int index = (list_index + i) % list_count;
|
||
|
ptr += sprintf(ptr, "{\"t\":%.2f,\"h\":%.2f}%s", _list[index].t,
|
||
|
_list[index].h, (i < list_count - 1) ? "," : "");
|
||
|
}
|
||
|
|
||
|
ptr += sprintf(ptr, "]");
|
||
|
response_body = history_body;
|
||
|
response_body_len = ptr - history_body;
|
||
|
sprintf(response_header, http_header_ok_json, response_body_len);
|
||
|
} else {
|
||
|
response_body = not_found_body;
|
||
|
response_body_len = strlen(not_found_body);
|
||
|
sprintf(response_header, http_header_not_found, response_body_len);
|
||
|
}
|
||
|
|
||
|
// Header senden
|
||
|
err_t wr_err = tcp_write(tpcb, response_header, strlen(response_header),
|
||
|
TCP_WRITE_FLAG_MORE);
|
||
|
if (wr_err != ERR_OK) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to write header %d\n", wr_err);
|
||
|
free(req);
|
||
|
pbuf_free(p);
|
||
|
return http_server_close(arg);
|
||
|
}
|
||
|
// Body senden
|
||
|
wr_err =
|
||
|
tcp_write(tpcb, response_body, response_body_len, TCP_WRITE_FLAG_COPY);
|
||
|
if (wr_err != ERR_OK) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to write body %d\n", wr_err);
|
||
|
free(req);
|
||
|
pbuf_free(p);
|
||
|
return http_server_close(arg);
|
||
|
}
|
||
|
tcp_output(tpcb); // Sicherstellen, dass Daten gesendet werden
|
||
|
if (history_body) {
|
||
|
free(history_body);
|
||
|
}
|
||
|
free(req);
|
||
|
}
|
||
|
pbuf_free(p);
|
||
|
tcp_recved(tpcb, p->tot_len); // Empfang der Daten bestätigen
|
||
|
return ERR_OK;
|
||
|
}
|
||
|
|
||
|
static err_t http_server_poll(void *arg, struct tcp_pcb *tpcb) {
|
||
|
// DEBUG_printf("HTTP server poll function called\n");
|
||
|
return ERR_OK;
|
||
|
}
|
||
|
|
||
|
static void http_server_err(void *arg, err_t err) {
|
||
|
if (err != ERR_ABRT) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("HTTP server error: %d\n", err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static err_t http_server_accept(void *arg, struct tcp_pcb *client_pcb,
|
||
|
err_t err) {
|
||
|
HTTP_SERVER_T *state = (HTTP_SERVER_T *)arg;
|
||
|
if (err != ERR_OK || client_pcb == NULL) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failure in accept: %d\n", err);
|
||
|
return ERR_VAL;
|
||
|
}
|
||
|
DEBUG_printf("Client connected\n");
|
||
|
|
||
|
state->client_pcb = client_pcb;
|
||
|
tcp_arg(client_pcb, state);
|
||
|
tcp_sent(client_pcb, http_server_sent);
|
||
|
tcp_recv(client_pcb, http_server_recv);
|
||
|
tcp_poll(client_pcb, http_server_poll, POLL_TIME_S * 2);
|
||
|
tcp_err(client_pcb, http_server_err);
|
||
|
|
||
|
return ERR_OK;
|
||
|
}
|
||
|
|
||
|
bool http_server_open(void *arg) {
|
||
|
HTTP_SERVER_T *state = (HTTP_SERVER_T *)arg;
|
||
|
DEBUG_printf("Starting HTTP server at %s on port %u\n",
|
||
|
ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT);
|
||
|
|
||
|
struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
|
||
|
if (!pcb) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to create pcb\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
err_t err = tcp_bind(pcb, NULL, TCP_PORT);
|
||
|
if (err) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to bind to port %u\n", TCP_PORT);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
state->server_pcb = tcp_listen_with_backlog(pcb, 1);
|
||
|
if (!state->server_pcb) {
|
||
|
__tcp_errors++;
|
||
|
DEBUG_printf("Failed to listen\n");
|
||
|
if (pcb) {
|
||
|
tcp_close(pcb);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
tcp_arg(state->server_pcb, state);
|
||
|
tcp_accept(state->server_pcb, http_server_accept);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Funktion zum Initialisieren und Ausführen des HTTP-Servers in der
|
||
|
// Hauptschleife
|
||
|
void run_http_server(void) {
|
||
|
static HTTP_SERVER_T *state = NULL;
|
||
|
|
||
|
if (!state) {
|
||
|
state = http_server_init();
|
||
|
if (!state) {
|
||
|
return;
|
||
|
}
|
||
|
if (!http_server_open(state)) {
|
||
|
http_server_close(state);
|
||
|
free(state);
|
||
|
state = NULL;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if PICO_CYW43_ARCH_POLL
|
||
|
cyw43_arch_poll();
|
||
|
cyw43_arch_wait_for_work_until(make_timeout_time_ms(1));
|
||
|
#else
|
||
|
sleep_ms(1);
|
||
|
#endif
|
||
|
}
|