Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 119 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,122 @@ Hinweis: Der Hostname des Gerätes kann in der ETS im Abschnitt Netzwerk unter m

### RP2040 OTA

Aktuelle ist der OTA Upload beim RP2040 noch nicht getestet.
Aktuelle ist der OTA Upload beim RP2040 noch nicht getestet.

## Webserver

Aktuell ist der Webserver verfügbar für folgenden Platformen:
- ESP32

### Defines
|Define|Description|
|---|---|
|WEBSERVER_BASE_URI|Setzt die Hauptseite. Standard: /openknx
|WEBSERVER_BASE_URI_USED|Wenn gesetzt, wird kein Handler für "/" registriert. (Umleitung auf "/WEBSERVER_BASE_URI")|


### Webdateien
Um automatisch html/js/css zu komprimieren und als Headerdatei
zur Verfügung zu stellen, muss folgendes in der .custom.ini eingetragen werden:
```ini
extra_scripts =
${env.extra_scripts}
lib/OFM-Network/scripts/pio/minimize.py
```
Die Dateien müssen im Ordner /www sein und werden dann in den include Ordner als
char array gespeichert.

### Automatische Ersetzung
Beim kompilieren kann in den Webdateien automatisch eine Ersetzung von verschiedenen Variablen erfolgen:
|Platzhalter|Ersetzt durch|Standardwert|
|---|---|---|
|#webserver#base-uri#|WEBSERVER_BASE_URI|/openknx

### Verwendung in Modulen
Ander Module können eigene Pages oder Handler hinzufügen.
Diese werden dann auch in der Übersichtsseite angezeigt.

#### Pages (empfohlen)
Pages bieten die Möglichkeit ohne einen Hander zu verbrauchen, eigene Webseiten zur Verfügung zu stellen. Die URL dazu baut immer auf der WEBSERVER_BASE_URI auf. Eine Angabe von /dali wäre also zum Beispiel unter /openknx/dali erreichbar.
Es werden auch alle anderen Anfragen weitergeleitet, die mit /openknx/dali beginnen (/openknx/dali.css oder /openknx/dali/monitor/lib.js)
```C++
WebserverPage _page = {
.uri = "/dali", // URL (Relativ von /<WEBSERVER_BASE_URI>), Gleichzeitig auch Link auf der Übersichtsseite
.name = "Dali Monitor", // Anzeigename auf der Übersichtsseite
.isVisible = true, // Soll der Handler auf der Übersichtseite angezeigt werden
.handler = page_handler, // Funktion welche die Anfragen bearbeitet
.arg = (void*)this // (Optional) Argument um die Modulinstanz zu übergeben
};
openknxNetwork.webserver.addPage(_page);

[...]

int MeinModul::page_handler(const char *uri, WebRequest *req, void *arg)
{
// uri wird ohne den prefix von WEBSERVER_BASE_URI angegeben
// die volle uri ist unter req->uri abrufbar
if(strcmp(uri, "/page") == 0)
{
std::string reponse = "Hier steht die Response drin";
// NULL Terminierte char arrays können auch ohne längenangabe hinzugefügt werden
req->setResponse("text/html", response.c_str());
req->addResponseHeader("Content-Encoding", "gzip");
return 0;
}
else if(strcmp(uri, "/dali.css") == 0)
{
req->setResponse("text/html", file_index_html, file_index_html_len);
req->addResponseHeader("Content-Encoding", "gzip");
return 0;
}
else if(strcmp(uri, "/dali.js") == 0)
{
[...]
}

req->setStatusCode(404);
return -1;
}
```

#### Links
Mit Links kann man auf einen Wiki Eintrag oder auch das Repo verweisen.
```C++
openknxNetwork.webserver.addLink("Wiki", "https://github.com/OpenKNX/OpenKNX.wiki");
```

#### Handler (ESP32 only)
Es kann unter Umständen hilfreich sein, einen eigenen Handler zu definieren.
Dieser kann dann zum Beispiel für Websocket verbindungen verwendet werden.
```C++
httpd_uri_t ws = {
.uri = "/", // URL der Anwendung (absoluter Pfad)
.method = HTTP_GET, // Anzeigename auf der Übersichtsseite
.handler = ws_handler, // Funktion welche die Anfragen bearbeitet
.user_ctx = this, // Argument um die Modulinstanz zu übergeben
.is_websocket = true // Angabe ob es sich um einen Websockethandler handelt
};
WebserverHandler _ws = {
.httpd = ws, // Angabe des httpd_uri_t
.uri = "/", // Angabe der URL für den Link auf der Übersichtsseite
.name = "Dali Websocket", // Anzeigename auf der Übersichtsseite
.isVisible = false // Soll der Handler auf der Übersichtseite angezeigt werden
};
openknxNetwork.addWebserverHandler(_ws);

[...]

static esp_err_t ws_handler(httpd_req_t *req)
{
IotGateway *gw = (IotGateway *)req->user_ctx;

if(req->method == HTTP_GET)
{
// Got new Websocket Connection
return ESP_OK
}

// Handle websocket packages
return ESP_OK;
}
```
130 changes: 130 additions & 0 deletions scripts/pio/minimize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
Import("env", "projenv")
import os
import gzip
import base64

class console_color:
BLUE = '\033[94m'
CYAN = '\033[96m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
END = '\033[0m'

print()
print("{}Minimizing files for Network-Webserver:{}".format(console_color.YELLOW, console_color.END))

webserver_base_uri = None
defines = projenv.get("CPPDEFINES", [])
for define in defines:
if isinstance(define, tuple) and define[0] == "WEBSERVER_BASE_URI":
webserver_base_uri = define[1]

if webserver_base_uri is None:
with open("lib/OFM-Network/src/Webserver/Base_Webserver.h", "r") as f:
content = f.read().splitlines()
for line in content:
if "#define WEBSERVER_BASE_URI" in line:
webserver_base_uri = line.split("WEBSERVER_BASE_URI")[1].strip().replace("\"", "")
break

if webserver_base_uri is None:
webserver_base_uri = "/web"

print(f" {console_color.BLUE}WEBSERVER_BASE_URI: {webserver_base_uri}{console_color.END}")

def minify_html(content):
# Entfernt überflüssige Leerzeichen und Zeilenumbrüche
return ' '.join(content.split())

def minify_js(content):
# Entfernt Kommentare und überflüssige Leerzeichen
lines = content.splitlines()
minified = []
for line in lines:
if line:
minified.append(line)
return ''.join(minified)

def minify_css(content):
# Entfernt Kommentare und überflüssige Leerzeichen
content = ''.join(content.splitlines()) # Zeilenumbrüche entfernen
return ''.join(part.split('/*')[0] for part in content.split('*/')).strip() # Mehrzeilige Kommentare entfernen

def process_files(input_dir):
if not os.path.exists(input_dir + "/www"):
return

print((" {}" + input_dir + "{}").format(console_color.CYAN, console_color.END))

file_dict = {}
for root, _, files in os.walk(input_dir + "/www"):
for file in files:
file_path = os.path.join(root, file)
with open(file_path, 'r') as f:
content = f.read()
if file.endswith('.html'):
minimized = minify_html(content)
elif file.endswith('.js'):
minimized = minify_js(content)
elif file.endswith('.css'):
minimized = minify_css(content)
else:
continue

size_old = os.path.getsize(file_path)

with open(file_path, 'r') as f:
content = f.read()

if "#webserver#" in content:
content = content.replace("#webserver#base-uri#", webserver_base_uri)
with open(file_path + ".temp", 'w') as f:
f.write(content)
file_path_temp = file_path + ".temp"
else:
file_path_temp = file_path

with open(file_path_temp, 'rb') as f_in:
compressed_data = gzip.compress(f_in.read())

compressed_data = bytearray(compressed_data)
# remove the timestamp
compressed_data[4] = 0
compressed_data[5] = 0
compressed_data[6] = 0
compressed_data[7] = 0

size_new = len(compressed_data)
print(f" {os.path.basename(file_path):<30}: {round(size_new / size_old * 100, 2)}% {size_old:>6} B -> {size_new:>6} B")
variable_name = os.path.basename(file_path).replace(".", "_")
output_file = input_dir + "/include/file_" + variable_name + ".h"

if file_path_temp != file_path:
os.remove(file_path_temp)

# Create the include directory if it doesn't exist
if not os.path.exists(os.path.join(input_dir, "include")):
os.makedirs(os.path.join(input_dir, "include"))

# Headerdatei schreiben
with open(output_file, 'w') as f_out:
f_out.write(f"#ifndef FILE_{variable_name.upper()}_H\n")
f_out.write(f"#define FILE_{variable_name.upper()}_H\n\n")
f_out.write(f"static const uint8_t file_{variable_name}[] = {{\n")

# Schreibe die Dateidaten als Hexadezimalwerte ins Array
for i, byte in enumerate(compressed_data):
f_out.write(f"0x{byte:02x},")
if (i + 1) % 12 == 0: # Zeilenumbruch nach 12 Werten
f_out.write("\n")
f_out.write("\n};\n\n")
f_out.write(f"static const unsigned int file_{variable_name}_len = {len(compressed_data)};\n")
f_out.write(f"#endif // {variable_name.upper()}_H\n")


subfolders = [f.path for f in os.scandir("lib/") if f.is_dir()]
for subfolder in subfolders:
process_files(subfolder)

print()
8 changes: 8 additions & 0 deletions src/NetworkModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,14 @@ void NetworkModule::loop(bool configured)

checkLinkStatus();
handleOTA();

#ifdef HAS_WEBSERVER
if(_firstLoop)
{
_firstLoop = false;
webserver.begin();
}
#endif
}

void NetworkModule::handleOTA()
Expand Down
18 changes: 17 additions & 1 deletion src/NetworkModule.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#pragma once

#ifndef OPENKNX_NET_IGNORE

#include "OpenKNX.h"
#include "strings.h"
#include <functional>
Expand All @@ -13,6 +16,11 @@
#elif defined(KNX_IP_WIFI)
#include <WiFi.h>
#endif
#include <esp_http_server.h>
#include <esp_err.h>
#include "versions.h"
#include "Webserver/ESP32_Webserver.h"
#define HAS_WEBSERVER
#elif defined(ARDUINO_ARCH_RP2040)
#ifndef OPENKNX_USB_EXCHANGE_IGNORE
#define HAS_USB
Expand Down Expand Up @@ -79,6 +87,7 @@ class NetworkModule : public OpenKNX::Module

#ifdef ARDUINO_ARCH_ESP32
void esp32NetworkEvent(arduino_event_id_t event);
ESP32_Webserver webserver;
#endif

private:
Expand All @@ -87,6 +96,9 @@ class NetworkModule : public OpenKNX::Module
uint8_t GetByteProperty(uint8_t PropertyId);
void SetByteProperty(uint8_t PropertyId, uint8_t value);

#ifdef HAS_WEBSERVER
bool _firstLoop = true;
#endif
bool _powerSave = false;
bool _ipShown = false;
bool _useStaticIP = false;
Expand Down Expand Up @@ -134,6 +146,10 @@ class NetworkModule : public OpenKNX::Module
#endif

std::vector<NetworkChangeCallback> _callback;


};

extern NetworkModule openknxNetwork;
extern NetworkModule openknxNetwork;

#endif
Loading