mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
Merge pull request #1813 from MichaelDvP/dev
scheduler fixes, additions
This commit is contained in:
@@ -121,7 +121,7 @@ const de: Translation = {
|
|||||||
READONLY: 'Nur-Lese-Modus aktivieren (blockiert alle ausgehenden EMS Tx Write-Befehle)',
|
READONLY: 'Nur-Lese-Modus aktivieren (blockiert alle ausgehenden EMS Tx Write-Befehle)',
|
||||||
UNDERCLOCK_CPU: 'CPU-Geschwindigkeit untertakten',
|
UNDERCLOCK_CPU: 'CPU-Geschwindigkeit untertakten',
|
||||||
REMOTE_TIMEOUT: 'Timeout',
|
REMOTE_TIMEOUT: 'Timeout',
|
||||||
REMOTE_TIMEOUT_EN: 'Deaktitiere Remote bei fehender Temperatur',
|
REMOTE_TIMEOUT_EN: 'Deaktitiere Remote bei fehlender Temperatur',
|
||||||
HEATINGOFF: 'Heizen ausschalten beim EMS-ESP Start',
|
HEATINGOFF: 'Heizen ausschalten beim EMS-ESP Start',
|
||||||
MIN_DURATION: 'Dauer bis die Dusche erkannt wrid',
|
MIN_DURATION: 'Dauer bis die Dusche erkannt wrid',
|
||||||
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
|
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ const Scheduler: FC = () => {
|
|||||||
data={{
|
data={{
|
||||||
nodes: schedule
|
nodes: schedule
|
||||||
.filter((si) => !si.deleted)
|
.filter((si) => !si.deleted)
|
||||||
.sort((a, b) => a.time.localeCompare(b.time))
|
.sort((a, b) => a.cmd.localeCompare(b.cmd))
|
||||||
}}
|
}}
|
||||||
theme={schedule_theme}
|
theme={schedule_theme}
|
||||||
layout={{ custom: true }}
|
layout={{ custom: true }}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ const SchedulerDialog = ({
|
|||||||
name="time"
|
name="time"
|
||||||
label={isCondition ? 'Condition' : 'On Change Value'}
|
label={isCondition ? 'Condition' : 'On Change Value'}
|
||||||
fullWidth
|
fullWidth
|
||||||
value={editItem.time}
|
value={editItem.time == "00:00" ? editItem.time = "" : editItem.time}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
/>
|
||||||
@@ -307,7 +307,7 @@ const SchedulerDialog = ({
|
|||||||
name="time"
|
name="time"
|
||||||
type="time"
|
type="time"
|
||||||
label={isTimer ? LL.TIMER(1) : LL.TIME(1)}
|
label={isTimer ? LL.TIMER(1) : LL.TIME(1)}
|
||||||
value={editItem.time}
|
value={editItem.time == "" ? editItem.time = "00:00" : editItem.time}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2087,6 +2087,19 @@ let emsesp_customentities = {
|
|||||||
value_type: 0,
|
value_type: 0,
|
||||||
writeable: false,
|
writeable: false,
|
||||||
value: 0
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
ram: 1,
|
||||||
|
device_id: 0,
|
||||||
|
type_id: 0,
|
||||||
|
offset: 0,
|
||||||
|
factor: 1,
|
||||||
|
name: 'setpoint',
|
||||||
|
uom: 1,
|
||||||
|
value_type: 0,
|
||||||
|
writeable: true,
|
||||||
|
value: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -2129,11 +2142,29 @@ let emsesp_schedule = {
|
|||||||
cmd: 'system/restart',
|
cmd: 'system/restart',
|
||||||
value: '',
|
value: '',
|
||||||
name: 'auto_restart'
|
name: 'auto_restart'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
active: false,
|
||||||
|
flags: 130,
|
||||||
|
time: 'system/network info/rssi < -70',
|
||||||
|
cmd: 'system/restart',
|
||||||
|
value: '',
|
||||||
|
name: 'bad_wifi'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
active: false,
|
||||||
|
flags: 129,
|
||||||
|
time: 'boiler/outdoortemp',
|
||||||
|
cmd: 'boiler/selflowtemp',
|
||||||
|
value: '(custom/setpoint - boiler/outdoortemp) * 2.8 + 3',
|
||||||
|
name: 'heatingcurve'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// SCHEDULE
|
// MODULES
|
||||||
let emsesp_modules = {
|
let emsesp_modules = {
|
||||||
// 'modules': []
|
// 'modules': []
|
||||||
"modules": [
|
"modules": [
|
||||||
|
|||||||
@@ -320,8 +320,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
|
|||||||
// for example info, values, commands, etc
|
// for example info, values, commands, etc
|
||||||
bool single_command = (!value || !strlen(value));
|
bool single_command = (!value || !strlen(value));
|
||||||
if (single_command) {
|
if (single_command) {
|
||||||
// exception 1: anything that is from System
|
// exception: boiler coldshot command
|
||||||
// exception 2: boiler coldshot command
|
|
||||||
bool get_attributes = (!cf || !cf->cmdfunction_json_) && (strcmp(cmd, F_(coldshot)) != 0);
|
bool get_attributes = (!cf || !cf->cmdfunction_json_) && (strcmp(cmd, F_(coldshot)) != 0);
|
||||||
|
|
||||||
if (get_attributes) {
|
if (get_attributes) {
|
||||||
|
|||||||
@@ -1290,10 +1290,25 @@ bool System::get_value_info(JsonObject root, const char * command) {
|
|||||||
}
|
}
|
||||||
if (command_info("", 0, root)) {
|
if (command_info("", 0, root)) {
|
||||||
std::string s;
|
std::string s;
|
||||||
if (dash && root[cmd].containsKey(dash)) {
|
// Loop through all the key-value pairs in root to find the key case independent
|
||||||
s = root[cmd][dash].as<std::string>();
|
if (dash) { // search the nest first
|
||||||
} else if (root.containsKey(cmd)) {
|
for (JsonPair p : root) {
|
||||||
s = root[cmd].as<std::string>();
|
if (p.value().is<JsonObject>() && Helpers::toLower(p.key().c_str()) == cmd) {
|
||||||
|
for (JsonPair p1 : p.value().as<JsonObject>()) {
|
||||||
|
if (Helpers::toLower(p1.key().c_str()) == dash && !p1.value().is<JsonObject>()) {
|
||||||
|
s = p1.value().as<std::string>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (JsonPair p : root) {
|
||||||
|
if (Helpers::toLower(p.key().c_str()) == cmd && !p.value().is<JsonObject>()) {
|
||||||
|
s = p.value().as<std::string>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!s.empty()) {
|
if (!s.empty()) {
|
||||||
root.clear();
|
root.clear();
|
||||||
@@ -1306,7 +1321,7 @@ bool System::get_value_info(JsonObject root, const char * command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.clear();
|
root.clear();
|
||||||
LOG_ERROR("system command not found: %s from %s", cmd, command);
|
LOG_ERROR("system command '%s' not found", command);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ bool WebSchedulerService::onChange(const char * cmd) {
|
|||||||
#ifdef EMESESP_DEBUG
|
#ifdef EMESESP_DEBUG
|
||||||
// emsesp::EMSESP::logger().debug(scheduleItem.cmd.c_str());
|
// emsesp::EMSESP::logger().debug(scheduleItem.cmd.c_str());
|
||||||
#endif
|
#endif
|
||||||
return command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str());
|
return command(scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -381,13 +381,13 @@ bool WebSchedulerService::onChange(const char * cmd) {
|
|||||||
void WebSchedulerService::condition() {
|
void WebSchedulerService::condition() {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||||
auto match = compute(scheduleItem.time.c_str());
|
auto match = compute(scheduleItem.time);
|
||||||
#ifdef EMESESP_DEBUG
|
#ifdef EMESESP_DEBUG
|
||||||
// emsesp::EMSESP::logger().debug("condition match: %s", match.c_str());
|
// emsesp::EMSESP::logger().debug("condition match: %s", match.c_str());
|
||||||
#endif
|
#endif
|
||||||
if (!match.empty() && match[0] == '1') {
|
if (!match.empty() && match[0] == '1') {
|
||||||
if (scheduleItem.retry_cnt == 0xFF) { // default unswitched
|
if (scheduleItem.retry_cnt == 0xFF) { // default unswitched
|
||||||
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str()) ? 1 : 0xFF;
|
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()) ? 1 : 0xFF;
|
||||||
}
|
}
|
||||||
} else if (scheduleItem.retry_cnt == 1) {
|
} else if (scheduleItem.retry_cnt == 1) {
|
||||||
scheduleItem.retry_cnt = 0xFF;
|
scheduleItem.retry_cnt = 0xFF;
|
||||||
@@ -420,7 +420,7 @@ void WebSchedulerService::loop() {
|
|||||||
if (last_tm_min == -2) {
|
if (last_tm_min == -2) {
|
||||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
|
||||||
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str()) ? 0xFF : 0;
|
scheduleItem.retry_cnt = command(scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str()) ? 0xFF : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_tm_min = -1; // startup done, now use for RTC
|
last_tm_min = -1; // startup done, now use for RTC
|
||||||
@@ -438,7 +438,7 @@ void WebSchedulerService::loop() {
|
|||||||
// scheduled timer commands
|
// scheduled timer commands
|
||||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
|
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
|
||||||
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
|
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
|
||||||
command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str());
|
command(scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_uptime_min = uptime_min;
|
last_uptime_min = uptime_min;
|
||||||
@@ -455,7 +455,7 @@ void WebSchedulerService::loop() {
|
|||||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||||
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
|
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
|
||||||
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
|
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
|
||||||
command(scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()).c_str());
|
command(scheduleItem.cmd.c_str(), compute(scheduleItem.value).c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_tm_min = tm->tm_min;
|
last_tm_min = tm->tm_min;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class Token {
|
|||||||
const bool rightAssociative;
|
const bool rightAssociative;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// find tokens
|
||||||
std::deque<Token> exprToTokens(const std::string & expr) {
|
std::deque<Token> exprToTokens(const std::string & expr) {
|
||||||
std::deque<Token> tokens;
|
std::deque<Token> tokens;
|
||||||
|
|
||||||
@@ -204,7 +205,7 @@ std::deque<Token> exprToTokens(const std::string & expr) {
|
|||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort tokens to RPN form
|
||||||
std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
|
std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
|
||||||
std::deque<Token> queue;
|
std::deque<Token> queue;
|
||||||
std::vector<Token> stack;
|
std::vector<Token> stack;
|
||||||
@@ -282,8 +283,10 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
|
|||||||
stack.pop_back();
|
stack.pop_back();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case Token::Type::Unknown:
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +306,7 @@ std::deque<Token> shuntingYard(const std::deque<Token> & tokens) {
|
|||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if string is a number
|
||||||
bool isnum(const std::string & s) {
|
bool isnum(const std::string & s) {
|
||||||
if (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos)) {
|
if (s.find_first_not_of("0123456789.") == std::string::npos || (s[0] == '-' && s.find_first_not_of("0123456789.", 1) == std::string::npos)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -356,6 +360,7 @@ std::string commands(std::string & expr) {
|
|||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checks for logic value
|
||||||
int to_logic(const std::string & s) {
|
int to_logic(const std::string & s) {
|
||||||
if (s[0] == '1' || s == "on" || s == "ON" || s == "true") {
|
if (s[0] == '1' || s == "on" || s == "ON" || s == "true") {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -363,9 +368,10 @@ int to_logic(const std::string & s) {
|
|||||||
if (s[0] == '0' || s == "off" || s == "OFF" || s == "false") {
|
if (s[0] == '0' || s == "off" || s == "OFF" || s == "false") {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// number to string
|
||||||
std::string to_string(double d) {
|
std::string to_string(double d) {
|
||||||
if (d == static_cast<int>(d)) {
|
if (d == static_cast<int>(d)) {
|
||||||
return std::to_string(static_cast<int>(d));
|
return std::to_string(static_cast<int>(d));
|
||||||
@@ -373,8 +379,9 @@ std::string to_string(double d) {
|
|||||||
return std::to_string(d);
|
return std::to_string(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RPN calculator
|
||||||
std::string compute(const std::string & expr) {
|
std::string compute(const std::string & expr) {
|
||||||
auto expr_new = expr; //emsesp::Helpers::toLower(expr);
|
auto expr_new = emsesp::Helpers::toLower(expr);
|
||||||
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
||||||
commands(expr_new);
|
commands(expr_new);
|
||||||
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
// emsesp::EMSESP::logger().info("calculate: %s", expr_new.c_str());
|
||||||
@@ -413,10 +420,12 @@ std::string compute(const std::string & expr) {
|
|||||||
stack.push_back(to_string(-1 * std::stod(rhs)));
|
stack.push_back(to_string(-1 * std::stod(rhs)));
|
||||||
break;
|
break;
|
||||||
case '!':
|
case '!':
|
||||||
|
if (to_logic(rhs) < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
stack.push_back(to_logic(rhs) == 0 ? "1" : "0");
|
stack.push_back(to_logic(rhs) == 0 ? "1" : "0");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
case Token::Type::Compare: {
|
case Token::Type::Compare: {
|
||||||
if (stack.size() < 2) {
|
if (stack.size() < 2) {
|
||||||
@@ -483,6 +492,9 @@ std::string compute(const std::string & expr) {
|
|||||||
stack.pop_back();
|
stack.pop_back();
|
||||||
const auto lhs = to_logic(stack.back());
|
const auto lhs = to_logic(stack.back());
|
||||||
stack.pop_back();
|
stack.pop_back();
|
||||||
|
if (rhs < 0 || lhs < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
switch (token.str[0]) {
|
switch (token.str[0]) {
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
@@ -532,9 +544,12 @@ std::string compute(const std::string & expr) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
case Token::Type::LeftParen:
|
||||||
|
case Token::Type::RightParen:
|
||||||
|
case Token::Type::Unknown:
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stack.back();
|
return stack.back();
|
||||||
|
|||||||
Reference in New Issue
Block a user