Add command WebRun (as WebQuery extension) (#21364)

* tasmota-1m32

* ready

* add code usage

* clean

* remove also that

* remove WebQueryWithFunction in favor of default value
This commit is contained in:
Barbudor 2024-05-18 22:15:16 +02:00 committed by GitHub
parent 33b0c1d5c2
commit 60a42f015e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 188 additions and 116 deletions

View File

@ -461,6 +461,7 @@
#define D_CMND_WEBTIME "WebTime"
#define D_CMND_WEBSENSOR "WebSensor"
#define D_CMND_WEBGETCONFIG "WebGetConfig"
#define D_CMND_WEBRUN "WebRun"
#define D_CMND_EMULATION "Emulation"
#define D_CMND_SENDMAIL "Sendmail"
#define D_CMND_CORS "CORS"

View File

@ -512,10 +512,10 @@ enum DevGroupShareItem { DGR_SHARE_POWER = 1, DGR_SHARE_LIGHT_BRI = 2, DGR_SHARE
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
SRC_THERMOSTAT, SRC_CHAT, SRC_TCL, SRC_BERRY, SRC_FILE, SRC_SSERIAL, SRC_USBCONSOLE, SRC_SO47, SRC_SENSOR, SRC_MAX };
SRC_THERMOSTAT, SRC_CHAT, SRC_TCL, SRC_BERRY, SRC_FILE, SRC_SSERIAL, SRC_USBCONSOLE, SRC_SO47, SRC_SENSOR, SRC_WEB, SRC_MAX };
const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|"
"Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote|Shutter|"
"Thermostat|Chat|TCL|Berry|File|SSerial|UsbConsole|SO47|Sensor";
"Thermostat|Chat|TCL|Berry|File|SSerial|UsbConsole|SO47|Sensor|Web";
const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 };

View File

@ -474,6 +474,7 @@
#define USE_ENHANCED_GUI_WIFI_SCAN // Enable Wi-Fi scan output with BSSID (+0k5 code)
// #define USE_WEBSEND_RESPONSE // Enable command WebSend response message (+1k code)
// #define USE_WEBGETCONFIG // Enable restoring config from external webserver (+0k6)
// #define USE_WEBRUN // Enable executing a tasmota command file from external web server (+0.4 code)
// #define USE_GPIO_VIEWER // Enable GPIO Viewer to see realtime GPIO states (+6k code)
// #define GV_SAMPLING_INTERVAL 100 // [GvSampling] milliseconds - Use Tasmota Scheduler (100) or Ticker (20..99,101..1000)
#define USE_EMULATION_HUE // Enable Hue Bridge emulation for Alexa (+14k code, +2k mem common)

View File

@ -3320,6 +3320,169 @@ bool CaptivePortal(void)
/*********************************************************************************************/
enum {QUERY_DEFAULT=0, QUERY_RUN};
int WebQuery(char *buffer, int query_function);
#ifdef USE_WEBRUN
char *WebRunBuffer = nullptr;
char *WebRunContext = nullptr;
bool WebRunMutex = false;
void WebRunLoop(void)
{
if (WebRunBuffer && !WebRunMutex && BACKLOG_EMPTY) {
WebRunMutex = true;
char *command = strtok_r(WebRunContext, "\n\r", &WebRunContext);
if (command) {
while (isspace(*command)) command++; // skip space
if (*command && ';' != *command)
ExecuteCommand(command, SRC_WEB);
} else {
free(WebRunBuffer);
WebRunBuffer = WebRunContext = nullptr;
}
WebRunMutex = false;
}
}
void WebRunInit(const char *command_buffer)
{
if (!WebRunBuffer) {
int len = strlen(command_buffer);
WebRunContext = WebRunBuffer = (char*)malloc(len+1);
if (WebRunBuffer) {
memcpy(WebRunBuffer, command_buffer, len);
WebRunBuffer[len] = 0;
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("WEBRUN: not enough memory"));
}
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("WEBRUN: previous not completed"));
}
}
#endif // #ifdef USE_WEBRUN
int WebQuery(char *buffer, int query_function = 0)
{
// http://192.168.1.1/path GET -> Sends HTTP GET http://192.168.1.1/path
// http://192.168.1.1/path POST {"some":"message"} -> Sends HTTP POST to http://192.168.1.1/path with body {"some":"message"}
// http://192.168.1.1/path PUT [Autorization: Bearer abcdxyz] potato -> Sends HTTP PUT to http://192.168.1.1/path with authorization header and body "potato"
// http://192.168.1.1/path PATCH patchInfo -> Sends HTTP PATCH to http://192.168.1.1/path with body "potato"
// Valid HTTP Commands: GET, POST, PUT, and PATCH
// An unlimited number of headers can be sent per request, and a body can be sent for all command types
// The body will be ignored if sending a GET command
#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS)
HTTPClientLight http;
#else // HTTP only
WiFiClient http_client;
HTTPClient http;
#endif
int status = WEBCMND_WRONG_PARAMETERS;
char *temp;
const char *url = strtok_r(buffer, " ", &temp);
const char *method = strtok_r(temp, " ", &temp);
if (url) {
#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS)
if (http.begin(UrlEncode(url))) {
#else // HTTP only
if (http.begin(http_client, UrlEncode(url))) {
#endif
char empty_body[1] = { 0 };
char *body = empty_body;
if (temp) { // There is a body and/or header
if (temp[0] == '[') { // Header information was sent; decode it
temp += 1;
temp = strtok_r(temp, "]", &body);
bool headerFound = true;
while (headerFound) {
char *header = strtok_r(temp, ":", &temp);
if (header) {
char *headerBody = strtok_r(temp, "|", &temp);
if (headerBody) {
http.addHeader(header, headerBody);
}
else headerFound = false;
}
else headerFound = false;
}
} else { // No header information was sent, but there was a body
body = temp;
}
}
int http_code;
if ((!method) || 0 == strcasecmp_P(method, PSTR("GET"))) { http_code = http.GET(); }
else if (0 == strcasecmp_P(method, PSTR("POST"))) { http_code = http.POST(body); }
else if (0 == strcasecmp_P(method, PSTR("PUT"))) { http_code = http.PUT(body); }
else if (0 == strcasecmp_P(method, PSTR("PATCH"))) { http_code = http.PATCH(body); }
else return status;
if (http_code > 0) { // http_code will be negative on error
#if defined(USE_WEBSEND_RESPONSE) || defined(USE_WEBRUN)
if (http_code == HTTP_CODE_OK || http_code == HTTP_CODE_MOVED_PERMANENTLY) {
// Return received data to the user - Adds 900+ bytes to the code
String response = http.getString(); // File found at server - may need lot of ram or trigger out of memory!
const char* read = response.c_str();
// uint32_t len = response.length() + 1;
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Response '%*_H' = %s"), len, (uint8_t*)read, read);
#ifdef USE_WEBRUN
if (QUERY_RUN == query_function)
WebRunInit(read);
#endif
#ifdef USE_WEBSEND_RESPONSE
char text[3] = { 0 }; // Make room foor double %
text[0] = *read++;
if (text[0] != '\0') {
Response_P(PSTR("{\"" D_CMND_WEBQUERY "\":"));
bool assume_json = (text[0] == '{') || (text[0] == '[');
if (!assume_json) { ResponseAppend_P(PSTR("\"")); }
while (text[0] != '\0') {
if (text[0] > 31) { // Remove control characters like linefeed
if ('%' == text[0]) { // Fix char string formatting for %
text[1] = '%';
}
if (assume_json) {
if (ResponseAppend_P(text) == ResponseSize()) { break; };
} else {
if (ResponseAppend_P(EscapeJSONString(text).c_str()) == ResponseSize()) { break; };
}
}
text[0] = *read++;
text[1] = '\0';
}
if (!assume_json) { ResponseAppend_P(PSTR("\"")); }
ResponseJsonEnd();
#ifdef USE_SCRIPT
// recursive call must be possible in this case
void script_setaflg(uint8_t flg);
script_setaflg(0);
#endif // USE_SCRIPT
status = WEBCMND_VALID_RESPONSE;
} else {
#endif // USE_WEBSEND_RESPONSE
status = WEBCMND_DONE;
}
} else
#endif // USE_WEBSEND_RESPONSE || USE_WEBRUN
status = WEBCMND_DONE;
} else {
status = WEBCMND_CONNECT_FAILED;
}
http.end(); // Clean up connection data
} else {
status = WEBCMND_HOST_NOT_FOUND;
}
}
return status;
}
int WebSend(char *buffer)
{
// [tasmota] POWER1 ON --> Sends http://tasmota/cm?cmnd=POWER1 ON
@ -3364,120 +3527,6 @@ int WebSend(char *buffer)
return status;
}
int WebQuery(char *buffer) {
// http://192.168.1.1/path GET -> Sends HTTP GET http://192.168.1.1/path
// http://192.168.1.1/path POST {"some":"message"} -> Sends HTTP POST to http://192.168.1.1/path with body {"some":"message"}
// http://192.168.1.1/path PUT [Autorization: Bearer abcdxyz] potato -> Sends HTTP PUT to http://192.168.1.1/path with authorization header and body "potato"
// http://192.168.1.1/path PATCH patchInfo -> Sends HTTP PATCH to http://192.168.1.1/path with body "potato"
// Valid HTTP Commands: GET, POST, PUT, and PATCH
// An unlimited number of headers can be sent per request, and a body can be sent for all command types
// The body will be ignored if sending a GET command
#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS)
HTTPClientLight http;
#else // HTTP only
WiFiClient http_client;
HTTPClient http;
#endif
int status = WEBCMND_WRONG_PARAMETERS;
char *temp;
char *url = strtok_r(buffer, " ", &temp);
char *method = strtok_r(temp, " ", &temp);
if (url && method) {
#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS)
if (http.begin(UrlEncode(url))) {
#else // HTTP only
if (http.begin(http_client, UrlEncode(url))) {
#endif
char empty_body[1] = { 0 };
char *body = empty_body;
if (temp) { // There is a body and/or header
if (temp[0] == '[') { // Header information was sent; decode it
temp += 1;
temp = strtok_r(temp, "]", &body);
bool headerFound = true;
while (headerFound) {
char *header = strtok_r(temp, ":", &temp);
if (header) {
char *headerBody = strtok_r(temp, "|", &temp);
if (headerBody) {
http.addHeader(header, headerBody);
}
else headerFound = false;
}
else headerFound = false;
}
} else { // No header information was sent, but there was a body
body = temp;
}
}
int http_code;
if (0 == strcasecmp_P(method, PSTR("GET"))) { http_code = http.GET(); }
else if (0 == strcasecmp_P(method, PSTR("POST"))) { http_code = http.POST(body); }
else if (0 == strcasecmp_P(method, PSTR("PUT"))) { http_code = http.PUT(body); }
else if (0 == strcasecmp_P(method, PSTR("PATCH"))) { http_code = http.PATCH(body); }
else return status;
if (http_code > 0) { // http_code will be negative on error
#ifdef USE_WEBSEND_RESPONSE
if (http_code == HTTP_CODE_OK || http_code == HTTP_CODE_MOVED_PERMANENTLY) {
// Return received data to the user - Adds 900+ bytes to the code
String response = http.getString(); // File found at server - may need lot of ram or trigger out of memory!
const char* read = response.c_str();
// uint32_t len = response.length() + 1;
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Response '%*_H' = %s"), len, (uint8_t*)read, read);
char text[3] = { 0 }; // Make room foor double %
text[0] = *read++;
if (text[0] != '\0') {
Response_P(PSTR("{\"" D_CMND_WEBQUERY "\":"));
bool assume_json = (text[0] == '{') || (text[0] == '[');
if (!assume_json) { ResponseAppend_P(PSTR("\"")); }
while (text[0] != '\0') {
if (text[0] > 31) { // Remove control characters like linefeed
if ('%' == text[0]) { // Fix char string formatting for %
text[1] = '%';
}
if (assume_json) {
if (ResponseAppend_P(text) == ResponseSize()) { break; };
} else {
if (ResponseAppend_P(EscapeJSONString(text).c_str()) == ResponseSize()) { break; };
}
}
text[0] = *read++;
text[1] = '\0';
}
if (!assume_json) { ResponseAppend_P(PSTR("\"")); }
ResponseJsonEnd();
#ifdef USE_SCRIPT
// recursive call must be possible in this case
void script_setaflg(uint8_t flg);
script_setaflg(0);
#endif // USE_SCRIPT
status = WEBCMND_VALID_RESPONSE;
} else {
status = WEBCMND_DONE;
}
} else
#endif // USE_WEBSEND_RESPONSE
status = WEBCMND_DONE;
} else {
status = WEBCMND_CONNECT_FAILED;
}
http.end(); // Clean up connection data
} else {
status = WEBCMND_HOST_NOT_FOUND;
}
}
return status;
}
#ifdef USE_WEBGETCONFIG
int WebGetConfig(char *buffer) {
// http://user:password@server:port/path/%id%.dmp : %id% will be expanded to MAC address
@ -3597,6 +3646,9 @@ const char kWebCommands[] PROGMEM = "|" // No prefix
#ifdef USE_WEBGETCONFIG
"|" D_CMND_WEBGETCONFIG
#endif
#ifdef USE_WEBRUN
"|" D_CMND_WEBRUN
#endif
#ifdef USE_CORS
"|" D_CMND_CORS
#endif
@ -3618,6 +3670,9 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_WEBGETCONFIG
, &CmndWebGetConfig
#endif
#ifdef USE_WEBRUN
, &CmndWebRun
#endif
#ifdef USE_CORS
, &CmndCors
#endif
@ -3743,6 +3798,18 @@ void CmndWebQuery(void) {
}
}
#ifdef USE_WEBRUN
void CmndWebRun(void) {
if (XdrvMailbox.data_len > 0) {
uint32_t result = WebQuery(XdrvMailbox.data, QUERY_RUN);
if (result != WEBCMND_VALID_RESPONSE) {
char stemp1[20];
ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebCmndStatus));
}
}
}
#endif // #ifdef USE_WEBRUN
#ifdef USE_WEBGETCONFIG
void CmndWebGetConfig(void) {
// WebGetConfig http://myserver:8000/tasmota/conf/%id%.dmp where %id% is expanded to device mac address
@ -3875,6 +3942,9 @@ bool Xdrv01(uint32_t function)
switch (function) {
case FUNC_LOOP:
PollDnsWebserver();
#ifdef USE_WEBRUN
WebRunLoop();
#endif // #ifdef USE_WEBRUN
#ifdef USE_EMULATION
if (Settings->flag2.emulation) { PollUdp(); }
#endif // USE_EMULATION