From 789fd1e055a3ca7da939fdc49417495797a81c50 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:58:33 +0100 Subject: [PATCH] Add command ``TimedPower`` Add command ``TimedPower [,ON|OFF|TOGGLE|BLINK]`` executes ``Power [ON|OFF|TOGGLE|BLINK] `` and after executes ``Power [OFF|ON|TOGGLE|OFF]`` --- CHANGELOG.md | 1 + RELEASENOTES.md | 1 + tasmota/include/i18n.h | 1 + tasmota/include/tasmota.h | 1 + tasmota/tasmota.ino | 7 ++ tasmota/tasmota_support/support_command.ino | 80 ++++++++++++++++++++- 6 files changed, 89 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d325929..f1ae70cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. - ESP32 MI BLE support for Xiaomi LYWSD02MMC (#20381) - LVGL option to add `lv.keyboard` extra widget (#20496) - GUI sensor separators (#20495) +- Command ``TimedPower [,ON|OFF|TOGGLE|BLINK]`` executes ``Power [ON|OFF|TOGGLE|BLINK] `` and after executes ``Power [OFF|ON|TOGGLE|OFF]`` ### Breaking Changed diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d5e4be0a8..183c4adeb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -118,6 +118,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm ## Changelog v13.3.0.3 ### Added +- Command ``TimedPower [,ON|OFF|TOGGLE|BLINK]`` executes ``Power [ON|OFF|TOGGLE|BLINK] `` and after executes ``Power [OFF|ON|TOGGLE|OFF]`` - Support for CST816S touch interface [#20213](https://github.com/arendst/Tasmota/issues/20213) - Support for Sonoff Basic R4 Magic Switch [#20247](https://github.com/arendst/Tasmota/issues/20247) - Display of active drivers using command ``status 4`` diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index a199861c5..e4fb16398 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -275,6 +275,7 @@ #define D_STATUS13_SHUTTER "SHT" #define D_CMND_STATE "State" #define D_CMND_POWER "Power" +#define D_CMND_TIMEDPOWER "TimedPower" #define D_CMND_FANSPEED "FanSpeed" #define D_CMND_POWERONSTATE "PowerOnState" #define D_CMND_PULSETIME "PulseTime" diff --git a/tasmota/include/tasmota.h b/tasmota/include/tasmota.h index 1feca3fef..b3fa0cbe4 100644 --- a/tasmota/include/tasmota.h +++ b/tasmota/include/tasmota.h @@ -222,6 +222,7 @@ const uint8_t SENSOR_MAX_MISS = 5; // Max number of missed sensor reads const uint8_t MAX_BACKLOG = 30; // Max number of commands in backlog const uint32_t MIN_BACKLOG_DELAY = 200; // Minimal backlog delay in mSeconds +const uint8_t MAX_TIMED_CMND = 16; // Max number of timed commands const uint32_t SOFT_BAUDRATE = 9600; // Default software serial baudrate const uint32_t APP_BAUDRATE = 115200; // Default serial baudrate diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 7febc7a3b..e7d4c455c 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -292,6 +292,11 @@ HardwareSerial TasConsole = Serial; // Only serial interface char EmptyStr[1] = { 0 }; // Provide a pointer destination to an empty char string +typedef struct { + uint32_t time; + String command; +} tTimedCmnd; + struct TasmotaGlobal_t { uint32_t global_update; // Timestamp of last global temperature and humidity update uint32_t baudrate; // Current Serial baudrate @@ -411,6 +416,7 @@ struct TasmotaGlobal_t { uint8_t backlog_pointer; // Command backlog pointer String backlog[MAX_BACKLOG]; // Command backlog buffer #endif + tTimedCmnd timed_cmnd[MAX_TIMED_CMND]; // Timed command buffer #ifdef MQTT_DATA_STRING String mqtt_data; // Buffer filled by Response functions @@ -851,6 +857,7 @@ void Scheduler(void) { static uint32_t state_50msecond = 0; // State 50msecond timer if (TimeReached(state_50msecond)) { SetNextTimeInterval(state_50msecond, 50); + LoopTimedCmnd(); #ifdef ROTARY_V1 RotaryHandler(); #endif // ROTARY_V1 diff --git a/tasmota/tasmota_support/support_command.ino b/tasmota/tasmota_support/support_command.ino index 2c5828786..89a5ecddd 100644 --- a/tasmota/tasmota_support/support_command.ino +++ b/tasmota/tasmota_support/support_command.ino @@ -23,7 +23,7 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix // Other commands D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|" D_CMND_SERIALLOG "|" D_CMND_RESTART "|" #ifndef FIRMWARE_MINIMAL_ONLY - D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_SLEEP "|" + D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_TIMEDPOWER "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_SLEEP "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" D_CMND_BLINKTIME "|" D_CMND_BLINKCOUNT "|" D_CMND_SAVEDATA "|" D_CMND_SO "|" D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|" D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|" @@ -63,7 +63,7 @@ SO_SYNONYMS(kTasmotaSynonyms, void (* const TasmotaCommand[])(void) PROGMEM = { &CmndUpgrade, &CmndUpgrade, &CmndOtaUrl, &CmndSeriallog, &CmndRestart, #ifndef FIRMWARE_MINIMAL_ONLY - &CmndBacklog, &CmndDelay, &CmndPower, &CmndStatus, &CmndState, &CmndSleep, + &CmndBacklog, &CmndDelay, &CmndPower, &CmndTimedPower, &CmndStatus, &CmndState, &CmndSleep, &CmndPowerOnState, &CmndPulsetime, &CmndBlinktime, &CmndBlinkcount, &CmndSavedata, &CmndSetoption, &CmndSetoption, &CmndTemperatureResolution, &CmndHumidityResolution, &CmndPressureResolution, &CmndPowerResolution, &CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution, @@ -493,6 +493,46 @@ void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len) { /********************************************************************************************/ +bool SetTimedCmnd(uint32_t time, const char *command) { + for (uint32_t i = 0; i < MAX_TIMED_CMND; i++) { + if (0 == TasmotaGlobal.timed_cmnd[i].time) { // Free slot + TasmotaGlobal.timed_cmnd[i].time = millis() + time; + if (0 == TasmotaGlobal.timed_cmnd[i].time) { // Skip empty slot flag + TasmotaGlobal.timed_cmnd[i].time++; + } + TasmotaGlobal.timed_cmnd[i].command = command; + return true; + } + } + AddLog(LOG_LEVEL_INFO, PSTR("TIM: No more timer slots left")); + return false; +} + +void ResetTimedCmnd(const char *command) { + for (uint32_t i = 0; i < MAX_TIMED_CMND; i++) { + if (TasmotaGlobal.timed_cmnd[i].time != 0) { + if (!strncmp(command, TasmotaGlobal.timed_cmnd[i].command.c_str(), strlen(command))) { + TasmotaGlobal.timed_cmnd[i].time = 0; + TasmotaGlobal.timed_cmnd[i].command = (const char*) nullptr; // Force deallocation of the String internal memory + } + } + } +} + +void LoopTimedCmnd(void) { + uint32_t now = millis(); + for (uint32_t i = 0; i < MAX_TIMED_CMND; i++) { + if ((TasmotaGlobal.timed_cmnd[i].time > 0) && (now > TasmotaGlobal.timed_cmnd[i].time)) { + TasmotaGlobal.timed_cmnd[i].time = 0; + String cmd = TasmotaGlobal.timed_cmnd[i].command; + TasmotaGlobal.timed_cmnd[i].command = (const char*) nullptr; // Force deallocation of the String internal memory + ExecuteCommand((char*)cmd.c_str(), SRC_TIMER); + } + } +} + +/********************************************************************************************/ + void CmndBacklog(void) { // Backlog command1;command2;.. Execute commands in sequence with a delay in between set with SetOption34 // Backlog0 command1;command2;.. Execute commands in sequence with no delay @@ -695,6 +735,42 @@ void CmndPower(void) } } +void CmndTimedPower(void) { + /* + TimedPower [,0|1|2|3] + TimedPower0 3000 - Turn all power on and then off after 3 seconds + TimedPower1 2000 - Turn power1 on and then off after 2 seconds + TimedPower2 2000,0 - Turn power2 off and then on after 2 seconds + TimedPower1 2200,1 - Turn power1 on and then off after 2.2 seconds + TimedPower2 2000,2 - Toggle power2 and then toggle again after 2 seconds + TimedPower2 2500,3 - Blink power2 and then turn off after 2.5 seconds + */ + if ((XdrvMailbox.index >= 0) && (XdrvMailbox.index <= TasmotaGlobal.devices_present)) { + if (XdrvMailbox.data_len > 0) { + uint32_t parm[2] = { 0, 0 }; + uint32_t parms = ParseParameters(2, parm); + uint32_t time = parm[0]; + uint32_t start_state = 1; // Default on + if (2 == parms) { + start_state = parm[1] & 0x03; // 0,1,2,3 + } + const uint8_t contra_state[] = { 1, 0, 2, 0 }; + uint32_t end_state = contra_state[start_state]; + + char cmnd[32]; + snprintf_P(cmnd, sizeof(cmnd), PSTR(D_CMND_POWER "%d %d"), XdrvMailbox.index, end_state); + if (SetTimedCmnd(time, cmnd)) { + XdrvMailbox.payload = start_state; + CmndPower(); + } + } else { + // Remove all POWER timed command + ResetTimedCmnd(D_CMND_POWER); + ResponseCmndDone(); + } + } +} + void CmndStatusResponse(uint32_t index) { static String all_status = (const char*) nullptr;