Files
firmware/src/mesh/http/ContentHandler.cpp

958 lines
36 KiB
C++
Raw Normal View History

#if !MESHTASTIC_EXCLUDE_WEBSERVER
#include "NodeDB.h"
#include "PowerFSM.h"
2021-03-27 01:00:27 -07:00
#include "RadioLibInterface.h"
#include "airtime.h"
#include "main.h"
#include "mesh/http/ContentHelper.h"
2022-01-05 22:12:32 -08:00
#include "mesh/http/WebServer.h"
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-08-06 10:35:54 -07:00
#include "Led.h"
#include "SPILock.h"
#include "power.h"
2024-07-25 20:50:42 -05:00
#include "serialization/JSON.h"
#include <FSCommon.h>
#include <HTTPBodyParser.hpp>
#include <HTTPMultipartBodyParser.hpp>
#include <HTTPURLEncodedBodyParser.hpp>
#ifdef ARCH_ESP32
#include "esp_task_wdt.h"
#endif
/*
Including the esp32_https_server library will trigger a compile time error. I've
tracked it down to a reoccurrance of this bug:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824
The work around is described here:
https://forums.xilinx.com/t5/Embedded-Development-Tools/Error-with-Standard-Libaries-in-Zynq/td-p/450032
Long story short is we need "#undef str" before including the esp32_https_server.
- Jm Casler (jm@casler.org) Oct 2020
*/
#undef str
// Includes for the https server
// https://github.com/fhessel/esp32_https_server
#include <HTTPRequest.hpp>
#include <HTTPResponse.hpp>
#include <HTTPSServer.hpp>
#include <HTTPServer.hpp>
#include <SSLCert.hpp>
// The HTTPS Server comes in a separate namespace. For easier use, include it here.
using namespace httpsserver;
#include "mesh/http/ContentHandler.h"
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
2022-01-05 19:44:21 -08:00
HTTPClient httpClient;
#define DEST_FS_USES_LITTLEFS
// 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"}, {"", ""}};
2022-04-15 23:16:40 -07:00
// const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
// Our API to handle messages to and from the radio.
HttpAPI webAPI;
void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
{
// For every resource available on the server, we need to create a ResourceNode
// The ResourceNode links URL and HTTP method to a handler function
ResourceNode *nodeAPIv1ToRadioOptions = new ResourceNode("/api/v1/toradio", "OPTIONS", &handleAPIv1ToRadio);
ResourceNode *nodeAPIv1ToRadio = new ResourceNode("/api/v1/toradio", "PUT", &handleAPIv1ToRadio);
ResourceNode *nodeAPIv1FromRadioOptions = new ResourceNode("/api/v1/fromradio", "OPTIONS", &handleAPIv1FromRadio);
ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio);
2022-10-31 18:47:10 +10:00
// ResourceNode *nodeHotspotApple = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
// ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleHotspot);
2021-10-11 00:11:04 +11:00
2022-01-07 20:20:47 -08:00
ResourceNode *nodeAdmin = new ResourceNode("/admin", "GET", &handleAdmin);
// ResourceNode *nodeAdminSettings = new ResourceNode("/admin/settings", "GET", &handleAdminSettings);
// ResourceNode *nodeAdminSettingsApply = new ResourceNode("/admin/settings/apply", "POST", &handleAdminSettingsApply);
2022-05-07 20:31:21 +10:00
// ResourceNode *nodeAdminFs = new ResourceNode("/admin/fs", "GET", &handleFs);
// ResourceNode *nodeUpdateFs = new ResourceNode("/admin/fs/update", "POST", &handleUpdateFs);
// ResourceNode *nodeDeleteFs = new ResourceNode("/admin/fs/delete", "GET", &handleDeleteFsContent);
ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart);
ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
2021-10-11 00:11:04 +11:00
ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
ResourceNode *nodeJsonNodes = new ResourceNode("/json/nodes", "GET", &handleNodes);
ResourceNode *nodeJsonFsBrowseStatic = new ResourceNode("/json/fs/browse/static", "GET", &handleFsBrowseStatic);
ResourceNode *nodeJsonDelete = new ResourceNode("/json/fs/delete/static", "DELETE", &handleFsDeleteStatic);
2021-10-11 00:11:04 +11:00
ResourceNode *nodeRoot = new ResourceNode("/*", "GET", &handleStatic);
// Secure nodes
secureServer->registerNode(nodeAPIv1ToRadioOptions);
secureServer->registerNode(nodeAPIv1ToRadio);
secureServer->registerNode(nodeAPIv1FromRadioOptions);
secureServer->registerNode(nodeAPIv1FromRadio);
2022-10-25 11:53:22 +02:00
// secureServer->registerNode(nodeHotspotApple);
// secureServer->registerNode(nodeHotspotAndroid);
secureServer->registerNode(nodeRestart);
secureServer->registerNode(nodeFormUpload);
secureServer->registerNode(nodeJsonScanNetworks);
secureServer->registerNode(nodeJsonBlinkLED);
secureServer->registerNode(nodeJsonFsBrowseStatic);
secureServer->registerNode(nodeJsonDelete);
secureServer->registerNode(nodeJsonReport);
secureServer->registerNode(nodeJsonNodes);
2022-05-07 20:31:21 +10:00
// secureServer->registerNode(nodeUpdateFs);
// secureServer->registerNode(nodeDeleteFs);
2022-01-07 20:20:47 -08:00
secureServer->registerNode(nodeAdmin);
2022-05-07 20:31:21 +10:00
// secureServer->registerNode(nodeAdminFs);
// secureServer->registerNode(nodeAdminSettings);
// secureServer->registerNode(nodeAdminSettingsApply);
secureServer->registerNode(nodeRoot); // This has to be last
// Insecure nodes
insecureServer->registerNode(nodeAPIv1ToRadioOptions);
insecureServer->registerNode(nodeAPIv1ToRadio);
insecureServer->registerNode(nodeAPIv1FromRadioOptions);
insecureServer->registerNode(nodeAPIv1FromRadio);
2022-10-25 11:53:22 +02:00
// insecureServer->registerNode(nodeHotspotApple);
// insecureServer->registerNode(nodeHotspotAndroid);
insecureServer->registerNode(nodeRestart);
insecureServer->registerNode(nodeFormUpload);
insecureServer->registerNode(nodeJsonScanNetworks);
insecureServer->registerNode(nodeJsonBlinkLED);
insecureServer->registerNode(nodeJsonFsBrowseStatic);
insecureServer->registerNode(nodeJsonDelete);
insecureServer->registerNode(nodeJsonReport);
2022-05-07 20:31:21 +10:00
// insecureServer->registerNode(nodeUpdateFs);
// insecureServer->registerNode(nodeDeleteFs);
2022-01-07 20:20:47 -08:00
insecureServer->registerNode(nodeAdmin);
2022-05-07 20:31:21 +10:00
// insecureServer->registerNode(nodeAdminFs);
// insecureServer->registerNode(nodeAdminSettings);
// insecureServer->registerNode(nodeAdminSettingsApply);
insecureServer->registerNode(nodeRoot); // This has to be last
}
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
{
LOG_DEBUG("webAPI handleAPIv1FromRadio");
/*
For documentation, see:
https://meshtastic.org/docs/development/device/http-api
https://meshtastic.org/docs/development/device/client-api
*/
// Get access to the parameters
ResourceParameters *params = req->getParams();
// std::string paramAll = "all";
std::string valueAll;
// Status code is 200 OK by default.
res->setHeader("Content-Type", "application/x-protobuf");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
2024-08-07 14:23:31 +02:00
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto");
if (req->getMethod() == "OPTIONS") {
res->setStatusCode(204); // Success with no content
// res->print(""); @todo remove
return;
}
uint8_t txBuf[MAX_STREAM_BUF_SIZE];
uint32_t len = 1;
if (params->getQueryParameter("all", valueAll)) {
// If all is true, return all the buffers we have available
// to us at this point in time.
if (valueAll == "true") {
while (len) {
len = webAPI.getFromRadio(txBuf);
res->write(txBuf, len);
}
// Otherwise, just return one protobuf
} else {
len = webAPI.getFromRadio(txBuf);
res->write(txBuf, len);
}
// the param "all" was not specified. Return just one protobuf
} else {
len = webAPI.getFromRadio(txBuf);
res->write(txBuf, len);
}
LOG_DEBUG("webAPI handleAPIv1FromRadio, len %d", len);
}
void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
{
LOG_DEBUG("webAPI handleAPIv1ToRadio");
/*
For documentation, see:
https://meshtastic.org/docs/development/device/http-api
https://meshtastic.org/docs/development/device/client-api
*/
res->setHeader("Content-Type", "application/x-protobuf");
res->setHeader("Access-Control-Allow-Headers", "Content-Type");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS");
2024-08-07 14:23:31 +02:00
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto");
if (req->getMethod() == "OPTIONS") {
res->setStatusCode(204); // Success with no content
2021-10-11 00:11:04 +11:00
// res->print(""); @todo remove
return;
}
byte buffer[MAX_TO_FROM_RADIO_SIZE];
size_t s = req->readBytes(buffer, MAX_TO_FROM_RADIO_SIZE);
LOG_DEBUG("Received %d bytes from PUT request", s);
webAPI.handleToRadio(buffer, s);
res->write(buffer, s);
LOG_DEBUG("webAPI handleAPIv1ToRadio");
}
2022-05-07 20:31:21 +10:00
void htmlDeleteDir(const char *dirname)
{
2022-03-15 22:49:06 +01:00
File root = FSCom.open(dirname);
2022-05-07 20:31:21 +10:00
if (!root) {
return;
}
2022-05-07 20:31:21 +10:00
if (!root.isDirectory()) {
return;
}
File file = root.openNextFile();
2022-05-07 20:31:21 +10:00
while (file) {
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
2022-03-15 22:49:06 +01:00
htmlDeleteDir(file.name());
file.flush();
file.close();
} else {
String fileName = String(file.name());
file.flush();
file.close();
LOG_DEBUG(" %s", fileName.c_str());
2022-03-15 22:49:06 +01:00
FSCom.remove(fileName);
}
file = root.openNextFile();
}
root.flush();
root.close();
}
2022-11-22 14:29:00 +01:00
JSONArray htmlListDir(const char *dirname, uint8_t levels)
{
2022-10-22 11:55:01 +02:00
File root = FSCom.open(dirname, FILE_O_READ);
2022-11-22 14:29:00 +01:00
JSONArray fileList;
2022-05-07 20:31:21 +10:00
if (!root) {
2022-11-22 14:29:00 +01:00
return fileList;
}
2022-05-07 20:31:21 +10:00
if (!root.isDirectory()) {
2022-11-22 14:29:00 +01:00
return fileList;
}
// iterate over the file list
File file = root.openNextFile();
2022-05-07 20:31:21 +10:00
while (file) {
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
2022-10-22 11:55:01 +02:00
#ifdef ARCH_ESP32
2022-11-22 14:29:00 +01:00
fileList.push_back(new JSONValue(htmlListDir(file.path(), levels - 1)));
2022-10-31 18:47:10 +10:00
#else
2022-11-22 14:29:00 +01:00
fileList.push_back(new JSONValue(htmlListDir(file.name(), levels - 1)));
2022-10-22 11:55:01 +02:00
#endif
file.close();
}
2022-05-07 20:31:21 +10:00
} else {
2022-11-22 14:29:00 +01:00
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
2022-10-31 18:47:10 +10:00
#ifdef ARCH_ESP32
2022-11-22 14:29:00 +01:00
thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
2022-10-22 11:55:01 +02:00
#else
2022-11-22 14:29:00 +01:00
thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
2022-10-22 11:55:01 +02:00
#endif
if (String(file.name()).substring(1).endsWith(".gz")) {
2022-10-22 11:55:01 +02:00
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
#else
String modifiedFile = String(file.name()).substring(1);
2022-10-22 11:55:01 +02:00
#endif
modifiedFile.remove((modifiedFile.length() - 3), 3);
2022-11-22 14:29:00 +01:00
thisFileMap["nameModified"] = new JSONValue(modifiedFile.c_str());
}
2022-11-22 14:29:00 +01:00
fileList.push_back(new JSONValue(thisFileMap));
}
file.close();
file = root.openNextFile();
}
root.close();
return fileList;
}
void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
concurrency::LockGuard g(spiLock);
2022-11-22 14:29:00 +01:00
auto fileList = htmlListDir("/static", 10);
// create json output structure
2022-11-22 14:29:00 +01:00
JSONObject filesystemObj;
filesystemObj["total"] = new JSONValue((int)FSCom.totalBytes());
filesystemObj["used"] = new JSONValue((int)FSCom.usedBytes());
filesystemObj["free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
2022-11-22 14:29:00 +01:00
JSONObject jsonObjInner;
jsonObjInner["files"] = new JSONValue(fileList);
jsonObjInner["filesystem"] = new JSONValue(filesystemObj);
2022-11-22 14:29:00 +01:00
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
2022-11-22 14:29:00 +01:00
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
2023-01-21 14:34:29 +01:00
delete value;
}
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
{
ResourceParameters *params = req->getParams();
std::string paramValDelete;
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "DELETE");
if (params->getQueryParameter("delete", paramValDelete)) {
std::string pathDelete = "/" + paramValDelete;
concurrency::LockGuard g(spiLock);
if (FSCom.remove(pathDelete.c_str())) {
LOG_INFO("%s", pathDelete.c_str());
2023-01-21 14:34:29 +01:00
JSONObject jsonObjOuter;
2022-11-22 14:29:00 +01:00
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
return;
} else {
LOG_INFO("%s", pathDelete.c_str());
2022-11-22 14:29:00 +01:00
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
return;
}
}
}
void handleStatic(HTTPRequest *req, HTTPResponse *res)
{
// Get access to the parameters
ResourceParameters *params = req->getParams();
std::string parameter1;
// Print the first parameter value
if (params->getPathParameter(0, parameter1)) {
std::string filename = "/static/" + parameter1;
std::string filenameGzip = "/static/" + parameter1 + ".gz";
// Try to open the file
File file;
2021-10-11 00:11:04 +11:00
bool has_set_content_type = false;
if (filename == "/static/") {
filename = "/static/index.html";
filenameGzip = "/static/index.html.gz";
}
2022-05-07 20:31:21 +10:00
concurrency::LockGuard g(spiLock);
if (FSCom.exists(filename.c_str())) {
file = FSCom.open(filename.c_str());
if (!file.available()) {
LOG_WARN("File not available - %s", filename.c_str());
}
} else if (FSCom.exists(filenameGzip.c_str())) {
file = FSCom.open(filenameGzip.c_str());
res->setHeader("Content-Encoding", "gzip");
if (!file.available()) {
LOG_WARN("File not available - %s", filenameGzip.c_str());
}
2021-10-11 00:11:04 +11:00
} else {
has_set_content_type = true;
filenameGzip = "/static/index.html.gz";
file = FSCom.open(filenameGzip.c_str());
2021-10-11 00:11:04 +11:00
res->setHeader("Content-Type", "text/html");
if (!file.available()) {
LOG_WARN("File not available - %s", filenameGzip.c_str());
res->println("Web server is running.<br><br>The content you are looking for can't be found. Please see: <a "
"href=https://meshtastic.org/docs/software/web-client/>FAQ</a>.<br><br><a "
"href=/admin>admin</a>");
return;
} else {
res->setHeader("Content-Encoding", "gzip");
}
}
res->setHeader("Content-Length", httpsserver::intToString(file.size()));
// Content-Type is guessed using the definition of the contentTypes-table defined above
int cTypeIdx = 0;
do {
if (filename.rfind(contentTypes[cTypeIdx][0]) != std::string::npos) {
res->setHeader("Content-Type", contentTypes[cTypeIdx][1]);
has_set_content_type = true;
break;
}
cTypeIdx += 1;
} while (strlen(contentTypes[cTypeIdx][0]) > 0);
if (!has_set_content_type) {
// Set a default content type
res->setHeader("Content-Type", "application/octet-stream");
}
// Read the file and write it to the HTTP response body
size_t length = 0;
do {
char buffer[256];
length = file.read((uint8_t *)buffer, 256);
std::string bufferString(buffer, length);
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
} while (length > 0);
file.close();
return;
} else {
LOG_ERROR("This should not have happened");
res->println("ERROR: This should not have happened");
}
}
void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
{
LOG_DEBUG("Form Upload - Disable keep-alive");
res->setHeader("Connection", "close");
// First, we need to check the encoding of the form that we have received.
// The browser will set the Content-Type request header, so we can use it for that purpose.
// Then we select the body parser based on the encoding.
// Actually we do this only for documentary purposes, we know the form is going
// to be multipart/form-data.
LOG_DEBUG("Form Upload - Creating body parser reference");
HTTPBodyParser *parser;
std::string contentType = req->getHeader("Content-Type");
// The content type may have additional properties after a semicolon, for example:
// Content-Type: text/html;charset=utf-8
// Content-Type: multipart/form-data;boundary=------s0m3w31rdch4r4c73rs
// As we're interested only in the actual mime _type_, we strip everything after the
// first semicolon, if one exists:
size_t semicolonPos = contentType.find(";");
if (semicolonPos != std::string::npos) {
contentType.resize(semicolonPos);
}
// Now, we can decide based on the content type:
if (contentType == "multipart/form-data") {
LOG_DEBUG("Form Upload - multipart/form-data");
parser = new HTTPMultipartBodyParser(req);
} else {
LOG_DEBUG("Unknown POST Content-Type: %s", contentType.c_str());
return;
}
res->println("<html><head><meta http-equiv=\"refresh\" content=\"1;url=/static\" /><title>File "
"Upload</title></head><body><h1>File Upload</h1>");
// We iterate over the fields. Any field with a filename is uploaded.
// Note that the BodyParser consumes the request body, meaning that you can iterate over the request's
// fields only a single time. The reason for this is that it allows you to handle large requests
// which would not fit into memory.
bool didwrite = false;
// parser->nextField() will move the parser to the next field in the request body (field meaning a
// form field, if you take the HTML perspective). After the last field has been processed, nextField()
// returns false and the while loop ends.
while (parser->nextField()) {
// For Multipart data, each field has three properties:
// The name ("name" value of the <input> tag)
// The filename (If it was a <input type="file">, this is the filename on the machine of the
// user uploading it)
// The mime type (It is determined by the client. So do not trust this value and blindly start
// parsing files only if the type matches)
std::string name = parser->getFieldName();
std::string filename = parser->getFieldFilename();
std::string mimeType = parser->getFieldMimeType();
// We log all three values, so that you can observe the upload on the serial monitor:
LOG_DEBUG("handleFormUpload: field name='%s', filename='%s', mimetype='%s'", name.c_str(), filename.c_str(),
mimeType.c_str());
// Double check that it is what we expect
if (name != "file") {
LOG_DEBUG("Skip unexpected field");
res->println("<p>No file found.</p>");
return;
}
// Double check that it is what we expect
if (filename == "") {
LOG_DEBUG("Skip unexpected field");
res->println("<p>No file found.</p>");
return;
}
// You should check file name validity and all that, but we skip that to make the core
// concepts of the body parser functionality easier to understand.
std::string pathname = "/static/" + filename;
concurrency::LockGuard g(spiLock);
// Create a new file to stream the data into
File file = FSCom.open(pathname.c_str(), FILE_O_WRITE);
size_t fileLength = 0;
didwrite = true;
// With endOfField you can check whether the end of field has been reached or if there's
// still data pending. With multipart bodies, you cannot know the field size in advance.
while (!parser->endOfField()) {
esp_task_wdt_reset();
byte buf[512];
size_t readLength = parser->read(buf, 512);
// LOG_DEBUG("readLength - %i", readLength);
// Abort the transfer if there is less than 50k space left on the filesystem.
if (FSCom.totalBytes() - FSCom.usedBytes() < 51200) {
file.flush();
file.close();
res->println("<p>Write aborted! Reserving 50k on filesystem.</p>");
// enableLoopWDT();
delete parser;
return;
}
// if (readLength) {
file.write(buf, readLength);
fileLength += readLength;
LOG_DEBUG("File Length %i", fileLength);
//}
}
// enableLoopWDT();
file.flush();
file.close();
res->printf("<p>Saved %d bytes to %s</p>", (int)fileLength, pathname.c_str());
}
if (!didwrite) {
res->println("<p>Did not write any file</p>");
}
res->println("</body></html>");
delete parser;
}
void handleReport(HTTPRequest *req, HTTPResponse *res)
{
ResourceParameters *params = req->getParams();
std::string content;
if (!params->getQueryParameter("content", content)) {
content = "json";
}
if (content == "json") {
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
} else {
res->setHeader("Content-Type", "text/html");
res->println("<pre>");
}
// data->airtime->tx_log
2022-11-22 14:29:00 +01:00
JSONArray txLogValues;
uint32_t *logArray;
2021-12-29 00:45:36 -08:00
logArray = airTime->airtimeReport(TX_LOG);
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
2022-11-22 14:29:00 +01:00
txLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime->rx_log
2022-11-22 14:29:00 +01:00
JSONArray rxLogValues;
2021-12-29 00:45:36 -08:00
logArray = airTime->airtimeReport(RX_LOG);
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
2022-11-22 14:29:00 +01:00
rxLogValues.push_back(new JSONValue((int)logArray[i]));
}
// data->airtime->rx_all_log
2022-11-22 14:29:00 +01:00
JSONArray rxAllLogValues;
2021-12-29 00:45:36 -08:00
logArray = airTime->airtimeReport(RX_ALL_LOG);
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
2022-11-22 14:29:00 +01:00
rxAllLogValues.push_back(new JSONValue((int)logArray[i]));
}
2022-11-22 14:29:00 +01:00
// data->airtime
JSONObject jsonObjAirtime;
jsonObjAirtime["tx_log"] = new JSONValue(txLogValues);
jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues);
jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues);
jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
jsonObjAirtime["seconds_per_period"] = new JSONValue(int(airTime->getSecondsPerPeriod()));
jsonObjAirtime["periods_to_log"] = new JSONValue(airTime->getPeriodsToLog());
// data->wifi
2022-11-22 14:29:00 +01:00
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
// data->memory
2022-11-22 14:29:00 +01:00
JSONObject jsonObjMemory;
jsonObjMemory["heap_total"] = new JSONValue((int)memGet.getHeapSize());
jsonObjMemory["heap_free"] = new JSONValue((int)memGet.getFreeHeap());
jsonObjMemory["psram_total"] = new JSONValue((int)memGet.getPsramSize());
jsonObjMemory["psram_free"] = new JSONValue((int)memGet.getFreePsram());
spiLock->lock();
2022-11-22 14:29:00 +01:00
jsonObjMemory["fs_total"] = new JSONValue((int)FSCom.totalBytes());
jsonObjMemory["fs_used"] = new JSONValue((int)FSCom.usedBytes());
jsonObjMemory["fs_free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
spiLock->unlock();
// data->power
2022-11-22 14:29:00 +01:00
JSONObject jsonObjPower;
jsonObjPower["battery_percent"] = new JSONValue(powerStatus->getBatteryChargePercent());
jsonObjPower["battery_voltage_mv"] = new JSONValue(powerStatus->getBatteryVoltageMv());
jsonObjPower["has_battery"] = new JSONValue(BoolToString(powerStatus->getHasBattery()));
jsonObjPower["has_usb"] = new JSONValue(BoolToString(powerStatus->getHasUSB()));
jsonObjPower["is_charging"] = new JSONValue(BoolToString(powerStatus->getIsCharging()));
// data->device
2022-11-22 14:29:00 +01:00
JSONObject jsonObjDevice;
jsonObjDevice["reboot_counter"] = new JSONValue((int)myNodeInfo.reboot_count);
// data->radio
2022-11-22 14:29:00 +01:00
JSONObject jsonObjRadio;
jsonObjRadio["frequency"] = new JSONValue(RadioLibInterface::instance->getFreq());
2023-03-25 20:14:04 +01:00
jsonObjRadio["lora_channel"] = new JSONValue((int)RadioLibInterface::instance->getChannelNum() + 1);
// collect data to inner data object
2022-11-22 14:29:00 +01:00
JSONObject jsonObjInner;
jsonObjInner["airtime"] = new JSONValue(jsonObjAirtime);
jsonObjInner["wifi"] = new JSONValue(jsonObjWifi);
jsonObjInner["memory"] = new JSONValue(jsonObjMemory);
jsonObjInner["power"] = new JSONValue(jsonObjPower);
jsonObjInner["device"] = new JSONValue(jsonObjDevice);
jsonObjInner["radio"] = new JSONValue(jsonObjRadio);
// create json output structure
2022-11-22 14:29:00 +01:00
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
2022-11-22 14:29:00 +01:00
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
2021-01-19 21:38:17 -08:00
}
void handleNodes(HTTPRequest *req, HTTPResponse *res)
{
ResourceParameters *params = req->getParams();
std::string content;
if (!params->getQueryParameter("content", content)) {
content = "json";
}
if (content == "json") {
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
} else {
res->setHeader("Content-Type", "text/html");
res->println("<pre>");
}
JSONArray nodesArray;
uint32_t readIndex = 0;
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
while (tempNodeInfo != NULL) {
if (tempNodeInfo->has_user) {
JSONObject node;
char id[16];
snprintf(id, sizeof(id), "!%08x", tempNodeInfo->num);
node["id"] = new JSONValue(id);
node["snr"] = new JSONValue(tempNodeInfo->snr);
node["via_mqtt"] = new JSONValue(BoolToString(tempNodeInfo->via_mqtt));
node["last_heard"] = new JSONValue((int)tempNodeInfo->last_heard);
node["position"] = new JSONValue();
if (nodeDB->hasValidPosition(tempNodeInfo)) {
JSONObject position;
position["latitude"] = new JSONValue((float)tempNodeInfo->position.latitude_i * 1e-7);
position["longitude"] = new JSONValue((float)tempNodeInfo->position.longitude_i * 1e-7);
position["altitude"] = new JSONValue((int)tempNodeInfo->position.altitude);
node["position"] = new JSONValue(position);
}
node["long_name"] = new JSONValue(tempNodeInfo->user.long_name);
node["short_name"] = new JSONValue(tempNodeInfo->user.short_name);
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", tempNodeInfo->user.macaddr[0],
tempNodeInfo->user.macaddr[1], tempNodeInfo->user.macaddr[2], tempNodeInfo->user.macaddr[3],
tempNodeInfo->user.macaddr[4], tempNodeInfo->user.macaddr[5]);
node["mac_address"] = new JSONValue(macStr);
node["hw_model"] = new JSONValue(tempNodeInfo->user.hw_model);
nodesArray.push_back(new JSONValue(node));
}
tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
}
// collect data to inner data object
JSONObject jsonObjInner;
jsonObjInner["nodes"] = new JSONValue(nodesArray);
// create json output structure
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
}
2021-01-19 21:38:17 -08:00
/*
This supports the Apple Captive Network Assistant (CNA) Portal
*/
void handleHotspot(HTTPRequest *req, HTTPResponse *res)
{
LOG_INFO("Hotspot Request");
2021-01-19 21:38:17 -08:00
/*
If we don't do a redirect, be sure to return a "Success" message
otherwise iOS will have trouble detecting that the connection to the SoftAP worked.
*/
// Status code is 200 OK by default.
// We want to deliver a simple HTML page, so we send a corresponding content type:
res->setHeader("Content-Type", "text/html");
2021-01-24 10:48:48 +11:00
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
2021-01-19 21:38:17 -08:00
// res->println("<!DOCTYPE html>");
res->println("<meta http-equiv=\"refresh\" content=\"0;url=/\" />");
2021-01-19 21:38:17 -08:00
}
void handleDeleteFsContent(HTTPRequest *req, HTTPResponse *res)
2022-01-05 22:12:32 -08:00
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
res->println("<h1>Meshtastic</h1>");
res->println("Delete Content in /static/*");
2022-01-05 22:12:32 -08:00
LOG_INFO("Delete files from /static/* : ");
2022-01-05 22:12:32 -08:00
concurrency::LockGuard g(spiLock);
2022-03-15 22:49:06 +01:00
htmlDeleteDir("/static");
2022-01-05 22:12:32 -08:00
res->println("<p><hr><p><a href=/admin>Back to admin</a>");
}
2022-01-07 20:20:47 -08:00
void handleAdmin(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
res->println("<h1>Meshtastic</h1>");
// res->println("<a href=/admin/settings>Settings</a><br>");
// res->println("<a href=/admin/fs>Manage Web Content</a><br>");
res->println("<a href=/json/report>Device Report</a><br>");
2022-01-07 20:20:47 -08:00
}
void handleAdminSettings(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
res->println("<h1>Meshtastic</h1>");
res->println("This isn't done.");
res->println("<form action=/admin/settings/apply method=post>");
res->println("<table border=1>");
res->println("<tr><td>Set?</td><td>Setting</td><td>current value</td><td>new value</td></tr>");
res->println("<tr><td><input type=checkbox></td><td>WiFi SSID</td><td>false</td><td><input type=radio></td></tr>");
res->println("<tr><td><input type=checkbox></td><td>WiFi Password</td><td>false</td><td><input type=radio></td></tr>");
2022-05-07 20:31:21 +10:00
res->println(
"<tr><td><input type=checkbox></td><td>Smart Position Update</td><td>false</td><td><input type=radio></td></tr>");
res->println("</table>");
res->println("<table>");
res->println("<input type=submit value=Apply New Settings>");
res->println("<form>");
res->println("<p><hr><p><a href=/admin>Back to admin</a>");
}
void handleAdminSettingsApply(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "POST");
res->println("<h1>Meshtastic</h1>");
res->println(
"<html><head><meta http-equiv=\"refresh\" content=\"1;url=/admin/settings\" /><title>Settings Applied. </title>");
res->println("Settings Applied. Please wait.");
}
void handleFs(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
2022-01-05 22:12:32 -08:00
res->println("<h1>Meshtastic</h1>");
res->println("<a href=/admin/fs/delete>Delete Web Content</a><p><form action=/admin/fs/update "
"method=post><input type=submit value=UPDATE_WEB_CONTENT></form>Be patient!");
res->println("<p><hr><p><a href=/admin>Back to admin</a>");
}
2021-01-19 21:38:17 -08:00
void handleRestart(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
2021-01-19 21:38:17 -08:00
res->println("<h1>Meshtastic</h1>");
2021-01-19 21:38:17 -08:00
res->println("Restarting");
LOG_DEBUG("Restarted on HTTP(s) Request");
2022-01-05 22:12:32 -08:00
webServerThread->requestRestart = (millis() / 1000) + 5;
2021-01-19 21:38:17 -08:00
}
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "POST");
2021-01-19 21:38:17 -08:00
ResourceParameters *params = req->getParams();
std::string blink_target;
if (!params->getQueryParameter("blink_target", blink_target)) {
// if no blink_target was supplied in the URL parameters of the
// POST request, then assume we should blink the LED
blink_target = "LED";
}
if (blink_target == "LED") {
uint8_t count = 10;
while (count > 0) {
Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-08-06 10:35:54 -07:00
ledBlink.set(true);
2021-01-19 21:38:17 -08:00
delay(50);
Finish powermon/powerstress (#4230) * Turn off vscode cmake prompt - we don't use cmake on meshtastic * Add rak4631_dap variant for debugging with NanoDAP debug probe device. * The rak device can also run freertos (which is underneath nrf52 arduino) * Add semihosting support for nrf52840 devices Initial platformio.ini file only supports rak4630 Default to non TCP for the semihosting log output for now... Fixes https://github.com/meshtastic/firmware/issues/4135 * powermon WIP (for https://github.com/meshtastic/firmware/issues/4136 ) * oops - mean't to mark the _dbg variant as an 'extra' board. * powermon wip * Make serial port on wio-sdk-wm1110 board work By disabling the (inaccessible) adafruit USB * Instrument (radiolib only for now) lora for powermon per https://github.com/meshtastic/firmware/issues/4136 * powermon gps support https://github.com/meshtastic/firmware/issues/4136 * Add CPU deep and light sleep powermon states https://github.com/meshtastic/firmware/issues/4136 * Change the board/swversion bootstring so it is a new "structured" log msg. * powermon wip * add example script for getting esp S3 debugging working Not yet used but I didn't want these nasty tricks to get lost yet. * Add PowerMon reporting for screen and bluetooth pwr. * make power.powermon_enables config setting work. * update to latest protobufs * fix bogus shellcheck warning * make powermon optional (but default enabled because tiny and no runtime impact) * tell vscode, if formatting, use whatever our trunk formatter wants without this flag if the user has set some other formatter (clang) in their user level settings, it will be looking in the wrong directory for the clang options (we want the options in .trunk/clang) Note: formatOnSave is true in master, which means a bunch of our older files are non compliant and if you edit them it will generate lots of formatting related diffs. I guess I'll start letting that happen with my future commits ;-). * add PowerStress module * nrf52 arduino is built upon freertos, so let platformio debug it * don't accidentally try to Segger ICE if we are using another ICE * clean up RedirectablePrint::log so it doesn't have three very different implementations inline. * remove NoopPrint - it is no longer needed * when talking to API clients via serial, don't turn off log msgs instead encapsuate them * fix the build - would loop forever if there were no files to send * don't use Segger code if not talking to a Segger debugger * when encapsulating logs, make sure the strings always has nul terminators * nrf52 soft device will watchdog if you use ICE while BT on... so have debugger disable bluetooth. * Important to not print debug messages while writing to the toPhone scratch buffer * don't include newlines if encapsulating log records as protobufs * update to latest protobufs (needed for powermon goo) * PowerStress WIP * for #4154 and #4136 add concept of dependent gpios... Which is currently only tested with the LED but eventually will be used for shared GPIO/screen power rail enable and LED forcing (which is a sanity check in the power stress testing) * fix linter warning * Transformer is a better name for the LED input > operation > output classes * PMW led changes to work on esp32-s3 * power stress improvements * allow ble logrecords to be fetched either by NOTIFY or INDICATE ble types This allows 'lossless' log reading. If client has requested INDICATE (rather than NOTIFY) each log record emitted via log() will have to fetched by the client device before the meshtastic node can continue. * Fix serious problem with nrf52 BLE logging. When doing notifies of LogRecords it is important to use the binary write routines - writing using the 'string' write won't work. Because protobufs can contain \0 nuls inside of them which if being parsed as a string will cause only a portion of the protobuf to be sent. I noticed this because some log messages were not getting through. * fix gpio transformer stuff to work correctly with LED_INVERTED Thanks @todd-herbert for noticing this and the great stack trace. The root cause was that I had accidentially shadowed outPin in a subclass with an unneeded override. It would break on any board that had inverted LED power. fixes https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 * Support driving multiple output gpios from one input. While investigating https://github.com/meshtastic/firmware/pull/4230#pullrequestreview-2217389099 I noticed in variant.h that there are now apparently newer TBEAMs than mine that have _both_ a GPIO based power LED and the PMU based LED. Add a splitter so that we can drive two output GPIOs from one logical signal. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2024-08-06 10:35:54 -07:00
ledBlink.set(false);
2021-01-19 21:38:17 -08:00
delay(50);
count = count - 1;
}
} else {
#if HAS_SCREEN
2021-01-19 21:38:17 -08:00
screen->blink();
2022-05-07 20:31:21 +10:00
#endif
2021-01-19 21:38:17 -08:00
}
2022-11-22 14:29:00 +01:00
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
2021-01-19 21:38:17 -08:00
}
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
2021-01-19 21:38:17 -08:00
// res->setHeader("Content-Type", "text/html");
int n = WiFi.scanNetworks();
// build list of network objects
2022-11-22 14:29:00 +01:00
JSONArray networkObjs;
if (n > 0) {
2021-01-19 21:38:17 -08:00
for (int i = 0; i < n; ++i) {
char ssidArray[50];
String ssidString = String(WiFi.SSID(i));
ssidString.replace("\"", "\\\"");
ssidString.toCharArray(ssidArray, 50);
if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {
2022-11-22 14:29:00 +01:00
JSONObject thisNetwork;
thisNetwork["ssid"] = new JSONValue(ssidArray);
thisNetwork["rssi"] = new JSONValue(int(WiFi.RSSI(i)));
2022-11-22 14:29:00 +01:00
networkObjs.push_back(new JSONValue(thisNetwork));
2021-01-19 21:38:17 -08:00
}
// Yield some cpu cycles to IP stack.
// This is important in case the list is large and it takes us time to return
// to the main loop.
yield();
}
}
// build output structure
2022-11-22 14:29:00 +01:00
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(networkObjs);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
2022-11-22 14:29:00 +01:00
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
}
#endif