Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command WebRun (as WebQuery extension) #21364

Merged
merged 6 commits into from
May 18, 2024
Merged
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
1 change: 1 addition & 0 deletions tasmota/include/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions tasmota/include/tasmota.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down
1 change: 1 addition & 0 deletions tasmota/my_user_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ An online tool to calculate TLS fingerprints is available here at:\n\
#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)
Expand Down
162 changes: 116 additions & 46 deletions tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino
Original file line number Diff line number Diff line change
Expand Up @@ -3320,51 +3320,51 @@ bool CaptivePortal(void)

/*********************************************************************************************/

int WebSend(char *buffer)
{
// [tasmota] POWER1 ON --> Sends http://tasmota/cm?cmnd=POWER1 ON
// [192.168.178.86:80,admin:joker] POWER1 ON --> Sends http://hostname:80/cm?user=admin&password=joker&cmnd=POWER1 ON
// [tasmota] /any/link/starting/with/a/slash.php?log=123 --> Sends http://tasmota/any/link/starting/with/a/slash.php?log=123
// [tasmota,admin:joker] /any/link/starting/with/a/slash.php?log=123 --> Sends http://tasmota/any/link/starting/with/a/slash.php?log=123

char *host;
char *user;
char *password;
char *command;
int status = WEBCMND_WRONG_PARAMETERS;
enum {QUERY_DEFAULT=0, QUERY_RUN};
int WebQuery(char *buffer, int query_function);

// buffer = | [ 192.168.178.86 : 80 , admin : joker ] POWER1 ON |
host = strtok_r(buffer, "]", &command); // host = | [ 192.168.178.86 : 80 , admin : joker |, command = | POWER1 ON |
if (host && command) {
RemoveSpace(host); // host = |[192.168.178.86:80,admin:joker|
host++; // host = |192.168.178.86:80,admin:joker| - Skip [
host = strtok_r(host, ",", &user); // host = |192.168.178.86:80|, user = |admin:joker|
String url = F("http://"); // url = |http://|
url += host; // url = |http://192.168.178.86:80|
#ifdef USE_WEBRUN
char *WebRunBuffer = nullptr;
char *WebRunContext = nullptr;
bool WebRunMutex = false;

command = Trim(command); // command = |POWER1 ON| or |/any/link/starting/with/a/slash.php?log=123|
if (command[0] != '/') {
url += F("/cm?"); // url = |http://192.168.178.86/cm?|
if (user) {
user = strtok_r(user, ":", &password); // user = |admin|, password = |joker|
if (user && password) {
char userpass[200];
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password);
url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&|
}
}
url += F("cmnd="); // url = |http://192.168.178.86/cm?cmnd=| or |http://192.168.178.86/cm?user=admin&password=joker&cmnd=|
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;
}
url += UrlEncode(command); // url = |http://192.168.178.86/cm?cmnd=POWER1%20ON|
url += F(" GET"); // url = |http://192.168.178.86/cm?cmnd=POWER1%20ON GET|
WebRunMutex = false;
}
}

DEBUG_CORE_LOG(PSTR("WEB: Uri '%s'"), url.c_str());
status = WebQuery(const_cast<char*>(url.c_str()));
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"));
}
return status;
}
#endif // #ifdef USE_WEBRUN


int WebQuery(char *buffer) {
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"
Expand All @@ -3384,10 +3384,10 @@ int WebQuery(char *buffer) {
int status = WEBCMND_WRONG_PARAMETERS;

char *temp;
char *url = strtok_r(buffer, " ", &temp);
char *method = strtok_r(temp, " ", &temp);
const char *url = strtok_r(buffer, " ", &temp);
const char *method = strtok_r(temp, " ", &temp);

if (url && method) {
if (url) {
#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS)
if (http.begin(UrlEncode(url))) {
#else // HTTP only
Expand Down Expand Up @@ -3417,22 +3417,25 @@ int WebQuery(char *buffer) {
}

int http_code;
if (0 == strcasecmp_P(method, PSTR("GET"))) { http_code = http.GET(); }
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
#ifdef USE_WEBSEND_RESPONSE
#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') {
Expand Down Expand Up @@ -3462,10 +3465,11 @@ int WebQuery(char *buffer) {
#endif // USE_SCRIPT
status = WEBCMND_VALID_RESPONSE;
} else {
#endif // USE_WEBSEND_RESPONSE
status = WEBCMND_DONE;
}
} else
#endif // USE_WEBSEND_RESPONSE
#endif // USE_WEBSEND_RESPONSE || USE_WEBRUN
status = WEBCMND_DONE;
} else {
status = WEBCMND_CONNECT_FAILED;
Expand All @@ -3478,6 +3482,51 @@ int WebQuery(char *buffer) {
return status;
}


int WebSend(char *buffer)
{
// [tasmota] POWER1 ON --> Sends http://tasmota/cm?cmnd=POWER1 ON
// [192.168.178.86:80,admin:joker] POWER1 ON --> Sends http://hostname:80/cm?user=admin&password=joker&cmnd=POWER1 ON
// [tasmota] /any/link/starting/with/a/slash.php?log=123 --> Sends http://tasmota/any/link/starting/with/a/slash.php?log=123
// [tasmota,admin:joker] /any/link/starting/with/a/slash.php?log=123 --> Sends http://tasmota/any/link/starting/with/a/slash.php?log=123

char *host;
char *user;
char *password;
char *command;
int status = WEBCMND_WRONG_PARAMETERS;

// buffer = | [ 192.168.178.86 : 80 , admin : joker ] POWER1 ON |
host = strtok_r(buffer, "]", &command); // host = | [ 192.168.178.86 : 80 , admin : joker |, command = | POWER1 ON |
if (host && command) {
RemoveSpace(host); // host = |[192.168.178.86:80,admin:joker|
host++; // host = |192.168.178.86:80,admin:joker| - Skip [
host = strtok_r(host, ",", &user); // host = |192.168.178.86:80|, user = |admin:joker|
String url = F("http://"); // url = |http://|
url += host; // url = |http://192.168.178.86:80|

command = Trim(command); // command = |POWER1 ON| or |/any/link/starting/with/a/slash.php?log=123|
if (command[0] != '/') {
url += F("/cm?"); // url = |http://192.168.178.86/cm?|
if (user) {
user = strtok_r(user, ":", &password); // user = |admin|, password = |joker|
if (user && password) {
char userpass[200];
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password);
url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&|
}
}
url += F("cmnd="); // url = |http://192.168.178.86/cm?cmnd=| or |http://192.168.178.86/cm?user=admin&password=joker&cmnd=|
}
url += UrlEncode(command); // url = |http://192.168.178.86/cm?cmnd=POWER1%20ON|
url += F(" GET"); // url = |http://192.168.178.86/cm?cmnd=POWER1%20ON GET|

DEBUG_CORE_LOG(PSTR("WEB: Uri '%s'"), url.c_str());
status = WebQuery(const_cast<char*>(url.c_str()));
}
return status;
}

#ifdef USE_WEBGETCONFIG
int WebGetConfig(char *buffer) {
// http://user:password@server:port/path/%id%.dmp : %id% will be expanded to MAC address
Expand Down Expand Up @@ -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
Expand All @@ -3618,6 +3670,9 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_WEBGETCONFIG
, &CmndWebGetConfig
#endif
#ifdef USE_WEBRUN
, &CmndWebRun
#endif
#ifdef USE_CORS
, &CmndCors
#endif
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down