mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-15 12:26:33 +03:00
Scheduler with type "Immediate", click Execute does not run the command
Fixes #3092
This commit is contained in:
@@ -297,10 +297,12 @@ const Scheduler = () => {
|
|||||||
{tableList.map((si: ScheduleItem) => (
|
{tableList.map((si: ScheduleItem) => (
|
||||||
<Row key={si.id} item={si} onClick={() => editScheduleItem(si)}>
|
<Row key={si.id} item={si} onClick={() => editScheduleItem(si)}>
|
||||||
<Cell stiff>
|
<Cell stiff>
|
||||||
|
{si.flags !== ScheduleFlag.SCHEDULE_IMMEDIATE && (
|
||||||
<CircleIcon
|
<CircleIcon
|
||||||
color={si.active ? 'success' : 'error'}
|
color={si.active ? 'success' : 'error'}
|
||||||
sx={{ fontSize: ICON_SIZE, verticalAlign: 'middle' }}
|
sx={{ fontSize: ICON_SIZE, verticalAlign: 'middle' }}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell stiff>
|
<Cell stiff>
|
||||||
<Stack spacing={0.5} direction="row">
|
<Stack spacing={0.5} direction="row">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -20,7 +21,9 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import { callAction } from '@/api/app';
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
|
import { useRequest } from 'alova/client';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||||
@@ -128,8 +131,15 @@ const SchedulerDialog = ({
|
|||||||
await handleSave(editItem);
|
await handleSave(editItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveandactivate = async () => {
|
const { send: executeSchedule } = useRequest(
|
||||||
await handleSave({ ...editItem, active: true });
|
(id: string) => callAction({ action: 'executeSchedule', param: id }),
|
||||||
|
{ immediate: false }
|
||||||
|
).onError((error) => {
|
||||||
|
toast.error(String(error.error?.message || 'An error occurred'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const execute = async () => {
|
||||||
|
await executeSchedule(editItem.name);
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
@@ -351,7 +361,7 @@ const SchedulerDialog = ({
|
|||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors || {}}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="name"
|
name="name"
|
||||||
label={LL.NAME(0) + ' (' + LL.OPTIONAL() + ')'}
|
label={LL.NAME(0)}
|
||||||
value={editItem.name}
|
value={editItem.name}
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -388,11 +398,11 @@ const SchedulerDialog = ({
|
|||||||
>
|
>
|
||||||
{creating ? LL.ADD(0) : LL.UPDATE()}
|
{creating ? LL.ADD(0) : LL.UPDATE()}
|
||||||
</Button>
|
</Button>
|
||||||
{isImmediateSchedule && editItem.cmd !== '' && (
|
{isImmediateSchedule && !creating && editItem.cmd !== '' && (
|
||||||
<Button
|
<Button
|
||||||
startIcon={<PlayArrowIcon />}
|
startIcon={<PlayArrowIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={saveandactivate}
|
onClick={execute}
|
||||||
color="success"
|
color="success"
|
||||||
>
|
>
|
||||||
{LL.EXECUTE()}
|
{LL.EXECUTE()}
|
||||||
|
|||||||
@@ -348,14 +348,14 @@ export enum DeviceEntityMask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ScheduleItem {
|
export interface ScheduleItem {
|
||||||
id: number; // unique index
|
id: number; // unique index for table
|
||||||
active: boolean;
|
active: boolean;
|
||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
flags: number;
|
flags: number;
|
||||||
time: string; // also used for Condition and On Change
|
time: string; // also used for Condition and On Change
|
||||||
cmd: string;
|
cmd: string;
|
||||||
value: string;
|
value: string;
|
||||||
name: string; // can be empty
|
name: string;
|
||||||
o_id?: number;
|
o_id?: number;
|
||||||
o_active?: boolean;
|
o_active?: boolean;
|
||||||
o_deleted?: boolean;
|
o_deleted?: boolean;
|
||||||
|
|||||||
@@ -232,7 +232,11 @@ export const schedulerItemValidation = (
|
|||||||
scheduleItem: ScheduleItem
|
scheduleItem: ScheduleItem
|
||||||
) =>
|
) =>
|
||||||
new Schema({
|
new Schema({
|
||||||
name: [NAME_PATTERN, uniqueNameValidator(schedule, scheduleItem.o_name)],
|
name: [
|
||||||
|
{ required: true, message: 'Name is required' },
|
||||||
|
NAME_PATTERN_REQUIRED,
|
||||||
|
uniqueNameValidator(schedule, scheduleItem.o_name)
|
||||||
|
],
|
||||||
cmd: [
|
cmd: [
|
||||||
{ required: true, message: 'Command is required' },
|
{ required: true, message: 'Command is required' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -393,6 +393,12 @@ function custom_support() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run a schedule
|
||||||
|
function executeSchedule(name: string) {
|
||||||
|
console.log('executing schedule', name);
|
||||||
|
return status(200);
|
||||||
|
}
|
||||||
|
|
||||||
// called by Action endpoint upgradeImportantMessages
|
// called by Action endpoint upgradeImportantMessages
|
||||||
function upgradeImportantMessages(version: string) {
|
function upgradeImportantMessages(version: string) {
|
||||||
// 0 is do nothing
|
// 0 is do nothing
|
||||||
@@ -5220,6 +5226,9 @@ router
|
|||||||
} else if (action === 'upgradeImportantMessages') {
|
} else if (action === 'upgradeImportantMessages') {
|
||||||
// check upgrade important messages
|
// check upgrade important messages
|
||||||
return upgradeImportantMessages(content.param);
|
return upgradeImportantMessages(content.param);
|
||||||
|
} else if (action === 'executeSchedule') {
|
||||||
|
// execute schedule
|
||||||
|
return executeSchedule(content.param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return status(404); // cmd not found
|
return status(404); // cmd not found
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
|
|||||||
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
||||||
publish();
|
publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// save new state to nvs #2946
|
// save new state to nvs #2946
|
||||||
if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
||||||
char key[sizeof(scheduleItem.name) + 2];
|
char key[sizeof(scheduleItem.name) + 2];
|
||||||
@@ -343,6 +344,7 @@ uint8_t WebSchedulerService::count_entities(bool cmd_only) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// execute scheduled command
|
// execute scheduled command
|
||||||
|
// return true if successful, false if not
|
||||||
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
||||||
std::string cmd = Helpers::toLower(command);
|
std::string cmd = Helpers::toLower(command);
|
||||||
|
|
||||||
@@ -365,130 +367,10 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
|||||||
std::string value = doc["value"] | data; // extract value if its in the command, or take the data
|
std::string value = doc["value"] | data; // extract value if its in the command, or take the data
|
||||||
std::string method = doc["method"] | "GET"; // default GET
|
std::string method = doc["method"] | "GET"; // default GET
|
||||||
commands(value, false);
|
commands(value, false);
|
||||||
if (value.length()) {
|
auto lower_url = Helpers::toLower(url.c_str());
|
||||||
method = "POST";
|
if (lower_url.starts_with("http://") || lower_url.starts_with("https://")) {
|
||||||
}
|
|
||||||
std::string result;
|
std::string result;
|
||||||
int httpResult = 0;
|
int httpResult = http_request(url, method, value, doc["header"].as<JsonObjectConst>(), result);
|
||||||
#ifndef NO_TLS_SUPPORT
|
|
||||||
if (Helpers::toLower(url.c_str()).starts_with("https://")) {
|
|
||||||
WiFiClient * basic_client = new WiFiClient;
|
|
||||||
ESP_SSLClient * ssl_client = new ESP_SSLClient;
|
|
||||||
ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert);
|
|
||||||
ssl_client->setBufferSizes(1024, 1024);
|
|
||||||
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
|
|
||||||
url.replace(0, 8, "");
|
|
||||||
std::string host = url;
|
|
||||||
auto index = url.find_first_of('/');
|
|
||||||
if (index != std::string::npos) {
|
|
||||||
host = url.substr(0, index);
|
|
||||||
url.replace(0, index, "");
|
|
||||||
}
|
|
||||||
// EMSESP::logger().debug("Host: %s, URL: %s", host.c_str(), url.c_str());
|
|
||||||
ssl_client->setClient(basic_client);
|
|
||||||
if (ssl_client->connect(host.c_str(), 443)) {
|
|
||||||
if (value.length() || Helpers::toLower(method) == "post") {
|
|
||||||
// EMSESP::logger().debug("POST %s HTTP/1.1", url.c_str());
|
|
||||||
ssl_client->print("POST ");
|
|
||||||
ssl_client->print(url.c_str());
|
|
||||||
ssl_client->println(" HTTP/1.1");
|
|
||||||
ssl_client->print("Host: ");
|
|
||||||
ssl_client->println(host.c_str());
|
|
||||||
bool content_set = false;
|
|
||||||
for (JsonPair p : doc["header"].as<JsonObject>()) {
|
|
||||||
content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type");
|
|
||||||
ssl_client->print(p.key().c_str());
|
|
||||||
ssl_client->print(": ");
|
|
||||||
ssl_client->println(p.value().as<std::string>().c_str());
|
|
||||||
}
|
|
||||||
if (!content_set) {
|
|
||||||
ssl_client->print("Content-Type: ");
|
|
||||||
if (value.starts_with('{')) {
|
|
||||||
ssl_client->println(asyncsrv::T_application_json);
|
|
||||||
} else {
|
|
||||||
ssl_client->println(asyncsrv::T_text_plain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ssl_client->print("Content-Length: ");
|
|
||||||
ssl_client->println(value.length());
|
|
||||||
ssl_client->println("Connection: close");
|
|
||||||
ssl_client->print("\r\n");
|
|
||||||
ssl_client->print(value.c_str());
|
|
||||||
} else {
|
|
||||||
// EMSESP::logger().debug("GET %s HTTP/1.1", url.c_str());
|
|
||||||
ssl_client->print("GET ");
|
|
||||||
ssl_client->print(url.c_str());
|
|
||||||
ssl_client->println(" HTTP/1.1");
|
|
||||||
ssl_client->print("Host: ");
|
|
||||||
ssl_client->println(host.c_str());
|
|
||||||
for (JsonPair p : doc["header"].as<JsonObject>()) {
|
|
||||||
ssl_client->print(p.key().c_str());
|
|
||||||
ssl_client->print(": ");
|
|
||||||
ssl_client->println(p.value().as<std::string>().c_str());
|
|
||||||
}
|
|
||||||
ssl_client->println("Connection: close");
|
|
||||||
}
|
|
||||||
auto ms = millis();
|
|
||||||
while (ssl_client->connected() && !ssl_client->available() && millis() - ms < 3000) {
|
|
||||||
delay(0);
|
|
||||||
}
|
|
||||||
while (ssl_client->available()) {
|
|
||||||
result += (char)ssl_client->read();
|
|
||||||
}
|
|
||||||
ssl_client->stop();
|
|
||||||
// EMSESP::logger().debug("HTTPS response: %s", result.c_str());
|
|
||||||
index = result.find_first_of(' ');
|
|
||||||
if (index != std::string::npos) {
|
|
||||||
httpResult = stoi(result.substr(index + 1, 3));
|
|
||||||
// EMSESP::logger().debug("HTTPS code: %i", httpResult);
|
|
||||||
}
|
|
||||||
index = result.find("\r\n\r\n");
|
|
||||||
if (index != std::string::npos) {
|
|
||||||
result.replace(0, index + 4, "");
|
|
||||||
// EMSESP::logger().debug("HTTPS response: %s", result.c_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
EMSESP::logger().warning("HTTPS connection failed");
|
|
||||||
}
|
|
||||||
delete ssl_client;
|
|
||||||
delete basic_client;
|
|
||||||
// check HTTP return code
|
|
||||||
if (httpResult != 200) {
|
|
||||||
EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
if (Helpers::toLower(url.c_str()).starts_with("http://")) {
|
|
||||||
HTTPClient * http = new HTTPClient;
|
|
||||||
if (http->begin(url.c_str())) {
|
|
||||||
bool content_set = false;
|
|
||||||
for (JsonPair p : doc["header"].as<JsonObject>()) {
|
|
||||||
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
|
|
||||||
content_set |= p.key() == "content-type";
|
|
||||||
}
|
|
||||||
// if there is data, force a POST
|
|
||||||
if (Helpers::toLower(method) == "post") { // we have all lowercase
|
|
||||||
if (!content_set) {
|
|
||||||
// http->addHeader("Content-Type", value.find_first_of('{') != std::string::npos ? "application/json" : "text/plain");
|
|
||||||
if (value.starts_with('{')) {
|
|
||||||
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
|
|
||||||
} else {
|
|
||||||
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_text_plain, false); // auto-set to JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
httpResult = http->POST(value.c_str());
|
|
||||||
} else {
|
|
||||||
httpResult = http->GET(); // normal GET
|
|
||||||
if (httpResult > 0) {
|
|
||||||
result = http->getString().c_str();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http->end();
|
|
||||||
delete http;
|
|
||||||
// check HTTP return code
|
|
||||||
if (httpResult != 200) {
|
if (httpResult != 200) {
|
||||||
EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult);
|
EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult);
|
||||||
return false;
|
return false;
|
||||||
@@ -598,7 +480,7 @@ void WebSchedulerService::loop() {
|
|||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
||||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
|
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
|
||||||
scheduleItem.active = false;
|
// scheduleItem.active = false;
|
||||||
publish_single(scheduleItem.name, false);
|
publish_single(scheduleItem.name, false);
|
||||||
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
||||||
publish();
|
publish();
|
||||||
@@ -659,6 +541,18 @@ void WebSchedulerService::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// execute a schedule item immediately
|
||||||
|
bool WebSchedulerService::executeSchedule(const char * name) {
|
||||||
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
|
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE && strcmp(scheduleItem.name, name) == 0) {
|
||||||
|
EMSESP::logger().info("Executing schedule '%s'", name);
|
||||||
|
return command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EMSESP::logger().warning("Schedule '%s' not found", name);
|
||||||
|
return false; // not found
|
||||||
|
}
|
||||||
|
|
||||||
// process schedules async
|
// process schedules async
|
||||||
void WebSchedulerService::scheduler_task(void * pvParameters) {
|
void WebSchedulerService::scheduler_task(void * pvParameters) {
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
|||||||
uint8_t count_entities(bool cmd_only = false);
|
uint8_t count_entities(bool cmd_only = false);
|
||||||
bool onChange(const char * cmd);
|
bool onChange(const char * cmd);
|
||||||
|
|
||||||
|
bool executeSchedule(const char * name);
|
||||||
|
|
||||||
std::string get_metrics_prometheus();
|
std::string get_metrics_prometheus();
|
||||||
|
|
||||||
std::string raw_value;
|
std::string raw_value;
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
|||||||
EMSESP::mqtt_.reset_mqtt();
|
EMSESP::mqtt_.reset_mqtt();
|
||||||
} else if (action == "upgradeImportantMessages") {
|
} else if (action == "upgradeImportantMessages") {
|
||||||
root["upgradeImportantMessageType"] = upgradeImportantMessages(param);
|
root["upgradeImportantMessageType"] = upgradeImportantMessages(param);
|
||||||
|
} else if (action == "executeSchedule") {
|
||||||
|
ok = EMSESP::webSchedulerService.executeSchedule(param.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||||
|
|||||||
Reference in New Issue
Block a user