sensor_main/include/server.h
2025-06-12 10:04:45 +02:00

460 lines
13 KiB
C
Executable File

#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
}