Skip to content

Commit

Permalink
Add command WebRun (as WebQuery extension) (#21364)
Browse files Browse the repository at this point in the history
* tasmota-1m32

* ready

* add code usage

* clean

* remove also that

* remove WebQueryWithFunction in favor of default value
  • Loading branch information
barbudor authored May 18, 2024
1 parent 33b0c1d commit 60a42f0
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 48 deletions.
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 @@ -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)
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

0 comments on commit 60a42f0

Please sign in to comment.