diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
index 2f9a99e583..377074e95f 100644
--- a/.github/workflows/package_raspbian.yml
+++ b/.github/workflows/package_raspbian.yml
@@ -23,6 +23,14 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/web
+ file: build.tar
+ target: build.tar
+ token: ${{ secrets.GITHUB_TOKEN }}
+
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
@@ -37,9 +45,12 @@ jobs:
- name: build .debpkg
run: |
+ mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/usr/lib/systemd/system/
+ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+ gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
@@ -52,7 +63,7 @@ jobs:
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: arm64
- depends: libyaml-cpp0.7
+ depends: libyaml-cpp0.7, openssl
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v3
diff --git a/.gitignore b/.gitignore
index 89f8ee065e..0f2202f8d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ venv/
release/
.vscode/extensions.json
/compile_commands.json
+src/mesh/raspihttp/certificate.pem
+src/mesh/raspihttp/private_key.pem
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 03922dc72b..e86d31c7d1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,7 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "trunk.io",
- "trunk.enableWindows": true
+ "trunk.enableWindows": true,
+ "files.insertFinalNewline": false,
+ "files.trimFinalNewlines": false
}
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index bf84dd9395..39935b8491 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -4,7 +4,7 @@ extends = arduino_base
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
build_src_filter =
- ${arduino_base.build_src_filter} - - - -
+ ${arduino_base.build_src_filter} - - - - -
upload_speed = 921600
debug_init_break = tbreak setup
diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini
index 3bde3465a1..5de0fa5497 100644
--- a/arch/esp32/esp32s2.ini
+++ b/arch/esp32/esp32s2.ini
@@ -2,7 +2,7 @@
extends = esp32_base
build_src_filter =
- ${esp32_base.build_src_filter} -
+ ${esp32_base.build_src_filter} - -
monitor_speed = 115200
@@ -12,5 +12,4 @@ build_flags =
lib_ignore =
${esp32_base.lib_ignore}
- NimBLE-Arduino
-
+ NimBLE-Arduino
\ No newline at end of file
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 04ca89a54d..5155eaadc8 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -11,7 +11,7 @@ build_flags =
-Isrc/platform/nrf52
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - -
lib_deps=
${arduino_base.lib_deps}
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index 0dcc9afc21..368fb5d0e3 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -12,6 +12,7 @@ build_src_filter =
-
-
-
+ +
-
-
-
diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini
index 48fe0dae65..edc4373ad4 100644
--- a/arch/rp2040/rp2040.ini
+++ b/arch/rp2040/rp2040.ini
@@ -12,7 +12,7 @@ build_flags =
-D__PLAT_RP2040__
# -D _POSIX_THREADS
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - -
lib_ignore =
BluetoothOTA
diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini
index 4483ff5262..4d74ade8fb 100644
--- a/arch/stm32/stm32wl5e.ini
+++ b/arch/stm32/stm32wl5e.ini
@@ -13,7 +13,7 @@ build_flags =
-DVECT_TAB_OFFSET=0x08000000
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - - - - - -
board_upload.offset_address = 0x08000000
upload_protocol = stlink
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index b5b105e4c2..a241a929a2 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -117,3 +117,7 @@ Input:
Logging:
LogLevel: info # debug, info, warn, error
+
+Webserver:
+# Port: 443 # Port for Webserver & Webservices
+# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
diff --git a/src/main.cpp b/src/main.cpp
index 6b542c0170..80706d044f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -68,6 +68,7 @@ NRF52Bluetooth *nrf52Bluetooth;
#ifdef ARCH_PORTDUINO
#include "linux/LinuxHardwareI2C.h"
+#include "mesh/raspihttp/PiWebServer.h"
#include "platform/portduino/PortduinoGlue.h"
#include
#include
@@ -862,6 +863,11 @@ void setup()
#endif
#ifdef ARCH_PORTDUINO
+#if __has_include()
+ if (settingsMap[webserverport] != -1) {
+ piwebServerThread = new PiWebServerThread();
+ }
+#endif
initApiServer(TCPPort);
#endif
diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp
new file mode 100644
index 0000000000..41f6727a4b
--- /dev/null
+++ b/src/mesh/raspihttp/PiWebServer.cpp
@@ -0,0 +1,530 @@
+/*
+Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices
+runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32
+Webserver libs including generation of SSL certifcicates, because the use ESP specific details in
+the lib that can't be emulated.
+
+The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio"
+The WebServer just adds basaic support to deliver WebContent, so it can be used to
+deliver the WebGui definded by the WebClient Project.
+
+Steps to get it running:
+1.) Add these Linux Libs to the compile and target machine:
+
+ sudo apt update && \
+ apt -y install openssl libssl-dev libopenssl libsdl2-dev \
+ libulfius-dev liborcania-dev
+
+2.) Configure the root directory of the web Content in the config.yaml file.
+ The followinng tags should be included and set at your needs
+
+ Example entry in the config.yaml
+ Webserver:
+ Port: 9001 # Port for Webserver & Webservices
+ RootPath: /home/marc/web # Root Dir of WebServer
+
+3.) Checkout the web project
+ https://github.com/meshtastic/web.git
+
+ Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath"
+
+!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!!
+
+Author: Marc Philipp Hammermann
+mail: marchammermann@googlemail.com
+
+*/
+#ifdef PORTDUINO_LINUX_HARDWARE
+#if __has_include()
+#include "PiWebServer.h"
+#include "NodeDB.h"
+#include "PhoneAPI.h"
+#include "PowerFSM.h"
+#include "RadioLibInterface.h"
+#include "airtime.h"
+#include "graphics/Screen.h"
+#include "main.h"
+#include "mesh/wifi/WiFiAPClient.h"
+#include "sleep.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "PortduinoFS.h"
+#include "platform/portduino/PortduinoGlue.h"
+
+#define DEFAULT_REALM "default_realm"
+#define PREFIX ""
+
+struct _file_config configWeb;
+
+// We need to specify some content-type mapping, so the resources get delivered with the
+// right content type and are displayed correctly in the browser
+char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
+ {".js", "text/javascript"}, {".png", "image/png"},
+ {".jpg", "image/jpg"}, {".gz", "application/gzip"},
+ {".gif", "image/gif"}, {".json", "application/json"},
+ {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
+ {".svg", "image/svg+xml"}, {".ts", "text/javascript"},
+ {".tsx", "text/javascript"}, {"", ""}};
+
+#undef str
+
+volatile bool isWebServerReady;
+volatile bool isCertReady;
+
+HttpAPI webAPI;
+
+PiWebServerThread *piwebServerThread;
+
+/**
+ * Return the filename extension
+ */
+const char *get_filename_ext(const char *path)
+{
+ const char *dot = strrchr(path, '.');
+ if (!dot || dot == path)
+ return "*";
+ if (strchr(dot, '?') != NULL) {
+ //*strchr(dot, '?') = '\0';
+ const char *empty = "\0";
+ return empty;
+ }
+ return dot;
+}
+
+/**
+ * Streaming callback function to ease sending large files
+ */
+static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max)
+{
+ (void)(pos);
+ if (cls != NULL) {
+ return fread(buf, 1, max, (FILE *)cls);
+ } else {
+ return U_STREAM_END;
+ }
+}
+
+/**
+ * Cleanup FILE* structure when streaming is complete
+ */
+static void callback_static_file_stream_free(void *cls)
+{
+ if (cls != NULL) {
+ fclose((FILE *)cls);
+ }
+}
+
+/**
+ * static file callback endpoint that delivers the content for WebServer calls
+ */
+int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data)
+{
+ size_t length;
+ FILE *f;
+ char *file_requested, *file_path, *url_dup_save, *real_path = NULL;
+ const char *content_type;
+
+ /*
+ * Comment this if statement if you don't access static files url from root dir, like /app
+ */
+ if (request->callback_position > 0) {
+ return U_CALLBACK_CONTINUE;
+ } else if (user_data != NULL && (configWeb.files_path != NULL)) {
+ file_requested = o_strdup(request->http_url);
+ url_dup_save = file_requested;
+
+ while (file_requested[0] == '/') {
+ file_requested++;
+ }
+ file_requested += o_strlen(configWeb.url_prefix);
+ while (file_requested[0] == '/') {
+ file_requested++;
+ }
+
+ if (strchr(file_requested, '#') != NULL) {
+ *strchr(file_requested, '#') = '\0';
+ }
+
+ if (strchr(file_requested, '?') != NULL) {
+ *strchr(file_requested, '?') = '\0';
+ }
+
+ if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) {
+ o_free(url_dup_save);
+ url_dup_save = file_requested = o_strdup("index.html");
+ }
+
+ file_path = msprintf("%s/%s", configWeb.files_path, file_requested);
+ real_path = realpath(file_path, NULL);
+ if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) {
+ if (access(file_path, F_OK) != -1) {
+ f = fopen(file_path, "rb");
+ if (f) {
+ fseek(f, 0, SEEK_END);
+ length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested));
+ if (content_type == NULL) {
+ content_type = u_map_get(&configWeb.mime_types, "*");
+ LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested));
+ }
+ u_map_put(response->map_header, "Content-Type", content_type);
+ u_map_copy_into(response->map_header, &configWeb.map_header);
+
+ if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free,
+ length, STATIC_FILE_CHUNK, f) != U_OK) {
+ LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n ");
+ }
+ }
+ } else {
+ if (configWeb.redirect_on_404 == NULL) {
+ ulfius_set_string_body_response(response, 404, "File not found");
+ } else {
+ ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
+ response->status = 302;
+ }
+ }
+ } else {
+ if (configWeb.redirect_on_404 == NULL) {
+ ulfius_set_string_body_response(response, 404, "File not found");
+ } else {
+ ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
+ response->status = 302;
+ }
+ }
+
+ o_free(file_path);
+ o_free(url_dup_save);
+ free(real_path); // realpath uses malloc
+ return U_CALLBACK_CONTINUE;
+ } else {
+ LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n");
+ return U_CALLBACK_ERROR;
+ }
+}
+
+static void handleWebResponse() {}
+
+/*
+ * Adapt the radioapi to the Webservice handleAPIv1ToRadio
+ * Trigger : WebGui(SAVE)->WebServcice->phoneApi
+ */
+int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
+{
+ LOG_DEBUG("handleAPIv1ToRadio web -> radio \n");
+
+ ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS");
+ ulfius_add_header_to_response(res, "X-Protobuf-Schema",
+ "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
+
+ if (req->http_verb == "OPTIONS") {
+ ulfius_set_response_properties(res, U_OPT_STATUS, 204);
+ return U_CALLBACK_CONTINUE;
+ }
+
+ byte buffer[MAX_TO_FROM_RADIO_SIZE];
+ size_t s = req->binary_body_length;
+
+ memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE);
+
+ // FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread
+
+ portduinoVFS->mountpoint("/home/marc/.portduino/default");
+
+ LOG_DEBUG("Received %d bytes from PUT request\n", s);
+ webAPI.handleToRadio(buffer, s);
+ LOG_DEBUG("end web->radio \n");
+ return U_CALLBACK_COMPLETE;
+}
+
+/*
+ * Adapt the radioapi to the Webservice handleAPIv1FromRadio
+ * Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events
+ */
+int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
+{
+
+ // LOG_DEBUG("handleAPIv1FromRadio radio -> web\n");
+ std::string valueAll;
+
+ // Status code is 200 OK by default.
+ ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
+ ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET");
+ ulfius_add_header_to_response(res, "X-Protobuf-Schema",
+ "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
+
+ uint8_t txBuf[MAX_STREAM_BUF_SIZE];
+ uint32_t len = 1;
+
+ if (valueAll == "true") {
+ while (len) {
+ len = webAPI.getFromRadio(txBuf);
+ ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len);
+ const char *tmpa = (const char *)txBuf;
+ ulfius_set_string_body_response(res, 200, tmpa);
+ // LOG_DEBUG("\n----webAPI response all:----\n");
+ LOG_DEBUG(tmpa);
+ LOG_DEBUG("\n");
+ }
+ // Otherwise, just return one protobuf
+ } else {
+ len = webAPI.getFromRadio(txBuf);
+ const char *tmpa = (const char *)txBuf;
+ ulfius_set_binary_body_response(res, 200, tmpa, len);
+ // LOG_DEBUG("\n----webAPI response:\n");
+ LOG_DEBUG(tmpa);
+ LOG_DEBUG("\n");
+ }
+
+ // LOG_DEBUG("end radio->web\n", len);
+ return U_CALLBACK_COMPLETE;
+}
+
+/*
+OpenSSL RSA Key Gen
+*/
+int generate_rsa_key(EVP_PKEY **pkey)
+{
+ EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (!pkey_ctx)
+ return -1;
+ if (EVP_PKEY_keygen_init(pkey_ctx) <= 0)
+ return -1;
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0)
+ return -1;
+ if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0)
+ return -1;
+ EVP_PKEY_CTX_free(pkey_ctx);
+ return 0; // SUCCESS
+}
+
+int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509)
+{
+ *x509 = X509_new();
+ if (!*x509)
+ return -1;
+ if (X509_set_version(*x509, 2) != 1)
+ return -1;
+ ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1);
+ X509_gmtime_adj(X509_get_notBefore(*x509), 0);
+ X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS
+
+ X509_set_pubkey(*x509, pkey);
+
+ // SET Subject Name
+ X509_NAME *name = X509_get_subject_name(*x509);
+ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0);
+ // Selfsigned, Issuer = Subject
+ X509_set_issuer_name(*x509, name);
+
+ // Certificate signed with our privte key
+ if (X509_sign(*x509, pkey, EVP_sha256()) <= 0)
+ return -1;
+
+ return 0;
+}
+
+char *read_file_into_string(const char *filename)
+{
+ FILE *file = fopen(filename, "rb");
+ if (file == NULL) {
+ LOG_ERROR("Error reading File : %s \n", filename);
+ return NULL;
+ }
+
+ // Size of file
+ fseek(file, 0, SEEK_END);
+ long filesize = ftell(file);
+ rewind(file);
+
+ // reserve mem for file + 1 byte
+ char *buffer = (char *)malloc(filesize + 1);
+ if (buffer == NULL) {
+ LOG_ERROR("Malloc of mem failed for file : %s \n", filename);
+ fclose(file);
+ return NULL;
+ }
+
+ // read content
+ size_t readSize = fread(buffer, 1, filesize, file);
+ if (readSize != filesize) {
+ LOG_ERROR("Error reading file into buffer\n");
+ free(buffer);
+ fclose(file);
+ return NULL;
+ }
+
+ // add terminator sign at the end
+ buffer[filesize] = '\0';
+ fclose(file);
+ return buffer; // return pointer
+}
+
+int PiWebServerThread::CheckSSLandLoad()
+{
+ // read certificate
+ cert_pem = read_file_into_string("certificate.pem");
+ if (cert_pem == NULL) {
+ LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n");
+ return 1;
+ }
+ // read private key
+ key_pem = read_file_into_string("private_key.pem");
+ if (key_pem == NULL) {
+ LOG_ERROR("ERROR file private_key can't be loaded or is missing\n");
+ return 2;
+ }
+
+ return 0;
+}
+
+int PiWebServerThread::CreateSSLCertificate()
+{
+
+ EVP_PKEY *pkey = NULL;
+ X509 *x509 = NULL;
+
+ if (generate_rsa_key(&pkey) != 0) {
+ LOG_ERROR("Error generating RSA-Key.\n");
+ return 1;
+ }
+
+ if (generate_self_signed_x509(pkey, &x509) != 0) {
+ LOG_ERROR("Error generating of X509-Certificat.\n");
+ return 2;
+ }
+
+ // Ope file to write private key file
+ FILE *pkey_file = fopen("private_key.pem", "wb");
+ if (!pkey_file) {
+ LOG_ERROR("Error opening private key file.\n");
+ return 3;
+ }
+ // write private key file
+ PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
+ fclose(pkey_file);
+
+ // open Certificate file
+ FILE *x509_file = fopen("certificate.pem", "wb");
+ if (!x509_file) {
+ LOG_ERROR("Error opening certificate.\n");
+ return 4;
+ }
+ // write cirtificate
+ PEM_write_X509(x509_file, x509);
+ fclose(x509_file);
+
+ EVP_PKEY_free(pkey);
+ X509_free(x509);
+ LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n");
+ return 0;
+}
+
+void initWebServer() {}
+
+PiWebServerThread::PiWebServerThread()
+{
+ int ret, retssl, webservport;
+
+ if (CheckSSLandLoad() != 0) {
+ CreateSSLCertificate();
+ if (CheckSSLandLoad() != 0) {
+ LOG_ERROR("Major Error Gen & Read SSL Certificate\n");
+ }
+ }
+
+ if (settingsMap[webserverport] != 0) {
+ webservport = settingsMap[webserverport];
+ LOG_INFO("Using webserver port from yaml config. %i \n", webservport);
+ } else {
+ LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n");
+ webservport = 443;
+ }
+
+ // Web Content Service Instance
+ if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) {
+ LOG_ERROR("Webserver couldn't be started, abort execution\n");
+ } else {
+
+ LOG_INFO("Webserver started ....\n");
+ u_map_init(&configWeb.mime_types);
+ u_map_put(&configWeb.mime_types, "*", "application/octet-stream");
+ u_map_put(&configWeb.mime_types, ".html", "text/html");
+ u_map_put(&configWeb.mime_types, ".htm", "text/html");
+ u_map_put(&configWeb.mime_types, ".tsx", "application/javascript");
+ u_map_put(&configWeb.mime_types, ".ts", "application/javascript");
+ u_map_put(&configWeb.mime_types, ".css", "text/css");
+ u_map_put(&configWeb.mime_types, ".js", "application/javascript");
+ u_map_put(&configWeb.mime_types, ".json", "application/json");
+ u_map_put(&configWeb.mime_types, ".png", "image/png");
+ u_map_put(&configWeb.mime_types, ".gif", "image/gif");
+ u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg");
+ u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg");
+ u_map_put(&configWeb.mime_types, ".ttf", "font/ttf");
+ u_map_put(&configWeb.mime_types, ".woff", "font/woff");
+ u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
+ u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
+
+ webrootpath = settingsStrings[webserverrootpath];
+
+ configWeb.files_path = (char *)webrootpath.c_str();
+ configWeb.url_prefix = "";
+ configWeb.rootPath = strdup(portduinoVFS->mountpoint());
+
+ u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*");
+ // Maximum body size sent by the client is 1 Kb
+ instanceWeb.max_post_body_size = 1024;
+ ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL);
+ ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath);
+
+ // Add callback function to all endpoints for the Web Server
+ ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb);
+
+ // thats for serving without SSL
+ // retssl = ulfius_start_framework(&instanceWeb);
+
+ // thats for serving with SSL
+ retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem);
+
+ if (retssl == U_OK) {
+ LOG_INFO("Web Server framework started on port: %i \n", webservport);
+ LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str());
+ } else {
+ LOG_ERROR("Error starting Web Server framework\n");
+ }
+ }
+}
+
+PiWebServerThread::~PiWebServerThread()
+{
+ u_map_clean(&configWeb.mime_types);
+
+ ulfius_stop_framework(&instanceWeb);
+ ulfius_stop_framework(&instanceWeb);
+ free(configWeb.rootPath);
+ ulfius_clean_instance(&instanceService);
+ ulfius_clean_instance(&instanceService);
+ free(cert_pem);
+ LOG_INFO("End framework");
+}
+
+#endif
+#endif
\ No newline at end of file
diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h
new file mode 100644
index 0000000000..c4c49e9197
--- /dev/null
+++ b/src/mesh/raspihttp/PiWebServer.h
@@ -0,0 +1,61 @@
+#pragma once
+#ifdef PORTDUINO_LINUX_HARDWARE
+#if __has_include()
+#include "PhoneAPI.h"
+#include "ulfius-cfg.h"
+#include "ulfius.h"
+#include
+#include
+
+#define STATIC_FILE_CHUNK 256
+
+void initWebServer();
+void createSSLCert();
+int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data);
+const char *get_filename_ext(const char *path);
+
+struct _file_config {
+ char *files_path;
+ char *url_prefix;
+ struct _u_map mime_types;
+ struct _u_map map_header;
+ char *redirect_on_404;
+ char *rootPath;
+};
+
+class PiWebServerThread
+{
+ private:
+ char *key_pem = NULL;
+ char *cert_pem = NULL;
+ // struct _u_map mime_types;
+ std::string webrootpath;
+
+ public:
+ PiWebServerThread();
+ ~PiWebServerThread();
+ int CreateSSLCertificate();
+ int CheckSSLandLoad();
+ uint32_t requestRestart = 0;
+ struct _u_instance instanceWeb;
+ struct _u_instance instanceService;
+};
+
+class HttpAPI : public PhoneAPI
+{
+
+ public:
+ // Nothing here yet
+
+ private:
+ // Nothing here yet
+
+ protected:
+ /// Check the current underlying physical link to see if the client is currently connected
+ virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
+};
+
+extern PiWebServerThread *piwebServerThread;
+
+#endif
+#endif
\ No newline at end of file
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index 046509faba..997058406a 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -195,6 +195,11 @@ void portduinoSetup()
settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as("");
}
+ if (yamlConfig["Webserver"]) {
+ settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as(-1);
+ settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as("");
+ }
+
} catch (YAML::Exception e) {
std::cout << "*** Exception " << e.what() << std::endl;
exit(EXIT_FAILURE);
diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index f8da20e37c..3fe5f74bf0 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -33,7 +33,10 @@ enum configNames {
displayOffsetY,
displayInvert,
keyboardDevice,
- logoutputlevel
+ logoutputlevel,
+ webserver,
+ webserverport,
+ webserverrootpath
};
enum { no_screen, st7789, st7735, st7735s, ili9341 };
enum { no_touchscreen, xpt2046, stmpe610 };
diff --git a/variants/portduino/platformio.ini b/variants/portduino/platformio.ini
index d37c6be21f..46417e388e 100644
--- a/variants/portduino/platformio.ini
+++ b/variants/portduino/platformio.ini
@@ -1,6 +1,10 @@
[env:native]
extends = portduino_base
-build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino
+; The pkg-config commands below optionally add link flags.
+; the || : is just a "or run the null command" to avoid returning an error code
+build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino -I /usr/include
+ !pkg-config --libs libulfius --silence-errors || :
+ !pkg-config --libs openssl --silence-errors || :
board = cross_platform
lib_deps = ${portduino_base.lib_deps}
-build_src_filter = ${portduino_base.build_src_filter}
\ No newline at end of file
+build_src_filter = ${portduino_base.build_src_filter}