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

Extended rules (up to 8 rules-sets) #19438

Closed
wants to merge 1 commit into from
Closed
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
5 changes: 5 additions & 0 deletions tasmota/include/tasmota.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ const uint8_t MAX_SHUTTER_KEYS = 4; // Max number of shutter keys or but
const uint8_t MAX_PCF8574 = 4; // Max number of PCF8574 devices
const uint8_t MAX_DS3502 = 4; // Max number of DS3502 digitsal potentiometer devices
const uint8_t MAX_IRSEND = 16; // Max number of IRSEND GPIOs
const uint8_t MAX_RULE_SETS_SETTINGS = 3; // Max number of rule sets of size 512 characters
#ifdef USE_RULES_EXTENDED
const uint8_t MAX_RULE_SETS = 8; // Max number of rule sets of size 512 characters
#else
const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters
#endif
const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules
const uint16_t VL53LXX_MAX_SENSORS = 8; // Max number of VL53L0X sensors

Expand Down
2 changes: 1 addition & 1 deletion tasmota/include/tasmota_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ typedef struct {
uint32_t energy_frequency_calibration; // 7C8 Also used by HX711 to save last weight
uint16_t web_refresh; // 7CC
char script_pram[5][10]; // 7CE
char rules[MAX_RULE_SETS][MAX_RULE_SIZE]; // 800 Uses 512 bytes in v5.12.0m, 3 x 512 bytes in v5.14.0b
char rules[MAX_RULE_SETS_SETTINGS][MAX_RULE_SIZE]; // 800 Uses 512 bytes in v5.12.0m, 3 x 512 bytes in v5.14.0b
TuyaFnidDpidMap tuya_fnid_map[MAX_TUYA_FUNCTIONS]; // E00 32 bytes
uint16_t ina226_r_shunt[4]; // E20
uint16_t ina226_i_fs[4]; // E28
Expand Down
4 changes: 4 additions & 0 deletions tasmota/my_user_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@
// #define USER_RULE1 "<Any rule1 data>" // Add rule1 data saved at initial firmware load or when command reset is executed
// #define USER_RULE2 "<Any rule2 data>" // Add rule2 data saved at initial firmware load or when command reset is executed
// #define USER_RULE3 "<Any rule3 data>" // Add rule3 data saved at initial firmware load or when command reset is executed
// USE_RULES_EXTENDED Allows up to 8 rules, requieres filesystem and specific compile settings
// Flash: +460 bytes, not counting the mandatory file system
// RAM : +2648 bytes
// IMPORTANT: IT CAN'T BE ENABLED HERE NOR IN USER_CONFIG_OVERRIDE.H - READ THE DOC

//#define USE_SCRIPT // Add support for script (+17k code)
// #define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support
Expand Down
144 changes: 115 additions & 29 deletions tasmota/tasmota_xdrv_driver/xdrv_10_rules.ino
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ struct RULES {
char event_data[RULE_MAX_EVENTSZ];
} Rules;

#ifdef USE_RULES_EXTENDED
struct {
uint32_t crc32;
char rules[MAX_RULE_SETS-MAX_RULE_SETS_SETTINGS][MAX_RULE_SIZE] = {{0,0},};
} RulesExt;
#endif

char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};

#if (MAX_RULE_VARS>16)
Expand All @@ -206,6 +213,24 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
#error MAX_RULE_MEMS is bigger than 16
#endif

/*******************************************************************************************/
/*
* RulePtr(idx)
*
* When extended rules are used, manage accessing the rule between Settings->rules and Rules->rules_ext
*
*******************************************************************************************/

#ifdef USE_RULES_EXTENDED
inline char *RulePtr(uint8_t idx) {
if (idx > 2)
return RulesExt.rules[(idx)-3];
return Settings->rules[(idx)];
}
#else
#define RulePtr(idx) Settings->rules[(idx)]
#endif


/*******************************************************************************************/
/*
Expand Down Expand Up @@ -237,14 +262,18 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};

#ifdef USE_UNISHOX_COMPRESSION
// Statically allocate one String per rule
#ifdef USE_RULES_EXTENDED
String k_rules[MAX_RULE_SETS] = { String(), String(), String(), String(), String(), String(), String(), String() }; // Strings are created empty
#else
String k_rules[MAX_RULE_SETS] = { String(), String(), String() }; // Strings are created empty
#endif
// Unishox compressor; // singleton
#endif // USE_UNISHOX_COMPRESSION

// Returns whether the rule is uncompressed, which means the first byte is not NULL
inline bool IsRuleUncompressed(uint32_t idx) {
#ifdef USE_UNISHOX_COMPRESSION
return Settings->rules[idx][0] ? true : false; // first byte not NULL, the rule is not empty and not compressed
return RulePtr(idx)[0] ? true : false; // first byte not NULL, the rule is not empty and not compressed
#else
return true;
#endif
Expand All @@ -253,32 +282,32 @@ inline bool IsRuleUncompressed(uint32_t idx) {
// Returns whether the rule is empty, which requires two consecutive NULL
inline bool IsRuleEmpty(uint32_t idx) {
#ifdef USE_UNISHOX_COMPRESSION
return (Settings->rules[idx][0] == 0) && (Settings->rules[idx][1] == 0) ? true : false;
return (RulePtr(idx)[0] == 0) && (RulePtr(idx)[1] == 0) ? true : false;
#else
return (Settings->rules[idx][0] == 0) ? true : false;
return (RulePtr(idx)[0] == 0) ? true : false;
#endif
}

// Returns the approximate (+3-0) length of the rule, not counting the trailing NULL
size_t GetRuleLen(uint32_t idx) {
// no need to use #ifdef USE_UNISHOX_COMPRESSION, the compiler will optimize since first test is always true
if (IsRuleUncompressed(idx)) {
return strlen(Settings->rules[idx]);
return strlen(RulePtr(idx));
} else { // either empty or compressed
return Settings->rules[idx][1] * 8; // cheap calculation, but not byte accurate (may overshoot by 7)
return RulePtr(idx)[1] * 8; // cheap calculation, but not byte accurate (may overshoot by 7)
}
}

// Returns the actual Flash storage for the Rule, including trailing NULL
size_t GetRuleLenStorage(uint32_t idx) {
#ifdef USE_UNISHOX_COMPRESSION
if (Settings->rules[idx][0] || !Settings->rules[idx][1]) { // if first byte is non-NULL it is uncompressed, if second byte is NULL, then it's either uncompressed or empty
return 1 + strlen(Settings->rules[idx]); // uncompressed or empty
if (RulePtr(idx)[0] || !RulePtr(idx)[1]) { // if first byte is non-NULL it is uncompressed, if second byte is NULL, then it's either uncompressed or empty
return 1 + strlen(RulePtr(idx)); // uncompressed or empty
} else {
return 2 + strlen(&Settings->rules[idx][1]); // skip first byte and get len of the compressed rule
return 2 + strlen(&RulePtr(idx)[1]); // skip first byte and get len of the compressed rule
}
#else
return 1 + strlen(Settings->rules[idx]);
return 1 + strlen(RulePtr(idx));
#endif
}

Expand All @@ -298,16 +327,16 @@ void GetRule_decompress(String &rule, const char *rule_head) {
// Returns: String() object containing a copy of the rule (rule processing is destructive and will change the String)
String GetRule(uint32_t idx) {
if (IsRuleUncompressed(idx)) {
return String(Settings->rules[idx]);
return String(RulePtr(idx));
} else {
#ifdef USE_UNISHOX_COMPRESSION // we still do #ifdef to make sure we don't link unnecessary code

String rule("");
if (Settings->rules[idx][1] == 0) { return rule; } // the rule is empty
if (RulePtr(idx)[1] == 0) { return rule; } // the rule is empty

// If the cache is empty, we need to decompress from Settings
if (0 == k_rules[idx].length() ) {
GetRule_decompress(rule, &Settings->rules[idx][1]);
GetRule_decompress(rule, &RulePtr(idx)[1]);
if (!Settings->flag4.compress_rules_cpu) {
k_rules[idx] = rule; // keep a copy for next time
}
Expand Down Expand Up @@ -354,7 +383,7 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) {
}
if (append) {
if (IsRuleUncompressed(idx) || IsRuleEmpty(idx)) { // if already uncompressed (so below 512) and append mode, check if it still fits uncompressed
offset = strlen(Settings->rules[idx]);
offset = strlen(RulePtr(idx));
if (len_in + offset >= MAX_RULE_SIZE) {
needsCompress = true;
}
Expand All @@ -364,19 +393,19 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) {
}

if (!needsCompress) { // the rule fits uncompressed, so just copy it
// strlcpy(Settings->rules[idx] + offset, content, sizeof(Settings->rules[idx]));
strlcpy(Settings->rules[idx] + offset, content, sizeof(Settings->rules[idx]) - offset);
if (0 == Settings->rules[idx][0]) {
Settings->rules[idx][1] = 0;
// strlcpy(RulePtr(idx) + offset, content, MAX_RULE_SIZE);
strlcpy(RulePtr(idx) + offset, content, MAX_RULE_SIZE - offset);
if (0 == RulePtr(idx)[0]) {
RulePtr(idx)[1] = 0;
}

#ifdef USE_UNISHOX_COMPRESSION
if (0 != len_in + offset) {
// do a dry-run compression to display how much it would be compressed
int32_t len_compressed, len_uncompressed;

len_uncompressed = strlen(Settings->rules[idx]);
len_compressed = compressor.unishox_compress(Settings->rules[idx], len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8);
len_uncompressed = strlen(RulePtr(idx));
len_compressed = compressor.unishox_compress(RulePtr(idx), len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8);
AddLog(LOG_LEVEL_INFO, PSTR("RUL: Stored uncompressed, would compress from %d to %d (-%d%%)"), len_uncompressed, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_uncompressed, 0, 100));
}

Expand All @@ -402,12 +431,12 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) {

if ((len_compressed >= 0) && (len_compressed < MAX_RULE_SIZE - 2)) {
// size is ok, copy to Settings
Settings->rules[idx][0] = 0; // clear first byte to mark as compressed
Settings->rules[idx][1] = (len_in + 7) / 8; // store original length in first bytes (4 bytes chuks)
memcpy(&Settings->rules[idx][2], buf_out, len_compressed);
Settings->rules[idx][len_compressed + 2] = 0; // add NULL termination
RulePtr(idx)[0] = 0; // clear first byte to mark as compressed
RulePtr(idx)[1] = (len_in + 7) / 8; // store original length in first bytes (4 bytes chuks)
memcpy(&RulePtr(idx)[2], buf_out, len_compressed);
RulePtr(idx)[len_compressed + 2] = 0; // add NULL termination
AddLog(LOG_LEVEL_INFO, PSTR("RUL: Compressed from %d to %d (-%d%%)"), len_in, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_in, 0, 100));
// AddLog(LOG_LEVEL_INFO, PSTR("RUL: First bytes: %02X%02X%02X%02X"), Settings->rules[idx][0], Settings->rules[idx][1], Settings->rules[idx][2], Settings->rules[idx][3]);
// AddLog(LOG_LEVEL_INFO, PSTR("RUL: First bytes: %02X%02X%02X%02X"), RulePtr(idx)[0], RulePtr(idx)[1], RulePtr(idx)[2], RulePtr(idx)[3]);
// AddLog(LOG_LEVEL_INFO, PSTR("RUL: GetRuleLenStorage = %d"), GetRuleLenStorage(idx));
} else {
len_compressed = -1; // failed
Expand Down Expand Up @@ -730,7 +759,7 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
delay(0); // Prohibit possible loop software watchdog

#ifdef DEBUG_RULES
AddLog(LOG_LEVEL_DEBUG, PSTR("RUL-RP1: Event = %s, Rule = %s"), event_saved.c_str(), Settings->rules[rule_set]);
AddLog(LOG_LEVEL_DEBUG, PSTR("RUL-RP1: Event = %s, Rule = %s"), event_saved.c_str(), RulePtr(rule_set));
#endif

String rules = GetRule(rule_set);
Expand Down Expand Up @@ -936,10 +965,14 @@ bool RulesProcess(void) {

void RulesInit(void)
{
// indicates scripter not enabled
bitWrite(Settings->rule_once, 7, 0);
// and indicates scripter do not use compress
bitWrite(Settings->rule_once, 6, 0);
#ifdef USE_RULES_EXTENDED
RulesLoadExtendedRules();
#else
// indicates scripter not enabled
bitWrite(Settings->rule_once, 7, 0);
// and indicates scripter do not use compress
bitWrite(Settings->rule_once, 6, 0);
#endif

TasmotaGlobal.rules_flag.data = 0;
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) {
Expand All @@ -951,6 +984,54 @@ void RulesInit(void)
Rules.teleperiod = false;
}

#ifdef USE_RULES_EXTENDED
void RulesLoadExtendedRules(void) {
bool erase = false;
#ifdef USE_UFILESYS
char filename[20];
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_10);
if (TfsLoadFile(filename, (uint8_t*)&RulesExt, sizeof(RulesExt))) {
AddLog(LOG_LEVEL_DEBUG, PSTR("RUL: Extended rules loaded from %s"), filename);
uint32_t crc32 = GetCfgCrc32((uint8_t*)RulesExt.rules, sizeof(RulesExt.rules));
if (RulesExt.crc32 != crc32) {
erase = true;
AddLog(LOG_LEVEL_ERROR, PSTR("RUL: Bad CRC, erasing"));
}
} else {
AddLog(LOG_LEVEL_ERROR, PSTR("RUL: Failed to load extended rules from %s"), filename);
erase = true;
}
#else // #ifdef USE_UFILESYS
AddLog(LOG_LEVEL_INFO, PSTR("RUL: Extended rules requires USE_UFILESYS"));
erase = true;
#endif // else of #ifdef USE_UFILESYS
if (erase) {
memset(&RulesExt, 0, sizeof(RulesExt));
RulesExt.crc32 = 1; // force save at next second
Settings->rule_enabled &= 0x07;
}
}

void RulesSaveExtendedRules(void)
{
uint32_t crc32 = GetCfgCrc32((uint8_t*)RulesExt.rules, sizeof(RulesExt.rules));
if (RulesExt.crc32 != crc32) {
RulesExt.crc32 = crc32;
#ifdef USE_UFILESYS
char filename[20];
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_10);
if (TfsSaveFile(filename, (uint8_t*)&RulesExt, sizeof(RulesExt))) {
AddLog(LOG_LEVEL_DEBUG, PSTR("RUL: Extended rules saved to %s"), filename);
} else {
AddLog(LOG_LEVEL_ERROR, PSTR("RUL: Failed to save extended rules to %s"), filename);
}
#else // #ifdef USE_UFILESYS
AddLog(LOG_LEVEL_INFO, PSTR("RUL: Extended rules requires USE_UFILESYS"));
#endif // else of #ifdef USE_UFILESYS
}
}
#endif // #ifdef USE_RULES_EXTENDED

void RulesEvery50ms(void)
{
if ((Settings->rule_enabled || BERRY_RULES) && !Rules.busy) { // Any rule enabled
Expand Down Expand Up @@ -2501,6 +2582,11 @@ bool Xdrv10(uint32_t function)
result = RulesMqttData();
break;
#endif // SUPPORT_MQTT_EVENT
#ifdef USE_RULES_EXTENDED
case FUNC_SAVE_SETTINGS:
RulesSaveExtendedRules();
break;
#endif
case FUNC_PRE_INIT:
RulesInit();
break;
Expand Down