From 5527f424a034438eaec4eb4093c92fafd2daee77 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Mon, 14 Oct 2019 16:12:59 +0200 Subject: [PATCH] Add deep sleep support Add define USE_DEEPSLEEP and command DeepSleepTime 0 or 10..86400 (seconds) to enter deepsleep mode (#6638) --- sonoff/_changelog.ino | 1 + sonoff/my_user_config.h | 1 + sonoff/settings.h | 14 ++- sonoff/sonoff.h | 2 +- sonoff/sonoff.ino | 23 +++-- sonoff/sonoff_template.h | 5 ++ sonoff/xdrv_29_deepsleep.ino | 160 +++++++++++++++++++++++++++++++++++ 7 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 sonoff/xdrv_29_deepsleep.ino diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 68ccc93dd..6d4a19f53 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -3,6 +3,7 @@ * Add command DimmerRange in Light module to support 2 byte dimming ranges from Tuya * Add Zigbee additional commands and sending messages to control devices (#6095) * Fix Rules were not triggered with IR unknown protocol or in sonoff-it (#6629) + * Add define USE_DEEPSLEEP and command DeepSleepTime 0 or 10..86400 (seconds) to enter deepsleep mode (#6638) * * 6.6.0.17 20191009 * Add command SetOption34 0..255 to set backlog delay. Default value is 200 (mSeconds) (#6562) diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index ad28116cf..d497605d4 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -316,6 +316,7 @@ #define USE_PS_16_DZ // Add support for PS-16-DZ Dimmer and Sonoff L1 (+2k code) //#define ROTARY_V1 // Add support for MI Desk Lamp //#define USE_SHUTTER // Add Shutter support for up to 4 shutter with different motortypes (+6k code) +//#define USE_DEEPSLEEP // Add support for deepsleep (+1k code) // -- Optional light modules ---------------------- #define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by // diff --git a/sonoff/settings.h b/sonoff/settings.h index d4ebfda01..b88b5ba45 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -384,10 +384,11 @@ struct SYSCFG { uint8_t shutter_position[MAX_SHUTTERS]; // E80 uint8_t shutter_startrelay[MAX_SHUTTERS]; // E84 uint8_t pcf8574_config[MAX_PCF8574]; // E88 - uint16_t dimmer_hw_min; // E8A - uint16_t dimmer_hw_max; // E8C + uint16_t dimmer_hw_min; // E90 + uint16_t dimmer_hw_max; // E92 + uint32_t deepsleep; // E94 - uint8_t free_e90[356]; // E90 + uint8_t free_e98[352]; // E98 uint32_t cfg_timestamp; // FF8 uint32_t cfg_crc32; // FFC @@ -408,7 +409,12 @@ struct RTCMEM { unsigned long pulse_counter[MAX_COUNTERS]; // 29C power_t power; // 2AC EnergyUsage energy_usage; // 2B0 - uint8_t free_038[36]; // 2C8 + unsigned long nextwakeup; // 2C8 + unsigned long uptime_old; // 2CC + uint32_t ultradeepsleep; // 2D0 + uint16_t deepsleep_slip; // 2D4 + + uint8_t free_022[22]; // 2D6 // 2EC - 2FF free locations } RtcSettings; diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index 2ec5267e0..c8c5a290d 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -269,7 +269,7 @@ enum LightTypes { LT_BASIC, LT_PWM1, LT_PWM2, LT_PWM3, LT_PWM4, LT enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FUNC_PRE_INIT, FUNC_INIT, FUNC_LOOP, FUNC_EVERY_50_MSECOND, FUNC_EVERY_100_MSECOND, FUNC_EVERY_200_MSECOND, FUNC_EVERY_250_MSECOND, FUNC_EVERY_300_MSECOND, FUNC_EVERY_SECOND, FUNC_SAVE_AT_MIDNIGHT, FUNC_SAVE_BEFORE_RESTART, - FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_SENSOR, FUNC_COMMAND, FUNC_COMMAND_SENSOR, FUNC_COMMAND_DRIVER, + FUNC_PREP_BEFORE_TELEPERIOD, FUNC_AFTER_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_SENSOR, FUNC_COMMAND, FUNC_COMMAND_SENSOR, FUNC_COMMAND_DRIVER, FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR, FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET, diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index b07b4c721..26030c107 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -107,8 +107,8 @@ int blinks = 201; // Number of LED blinks uint32_t uptime = 0; // Counting every second until 4294967295 = 130 year uint32_t loop_load_avg = 0; // Indicative loop load average uint32_t global_update = 0; // Timestamp of last global temperature and humidity update -uint32_t web_log_index = 1; // Index in Web log buffer (should never be 0) -float global_temperature = 9999; // Provide a global temperature to be used by some sensors +uint32_t web_log_index = 1; // Index in Web log buffer (should never be 0) +float global_temperature = 9999; // Provide a global temperature to be used by some sensors float global_humidity = 0; // Provide a global humidity to be used by some sensors float global_pressure = 0; // Provide a global pressure to be used by some sensors char *ota_url; // OTA url string pointer @@ -155,6 +155,7 @@ bool spi_flg = false; // SPI configured bool soft_spi_flg = false; // Software SPI configured bool ntp_force_sync = false; // Force NTP sync bool ntp_synced_message = false; // NTP synced message flag +bool prep_called = false; // Deep sleep flag to detect a proper start of initialize sensors myio my_module; // Active copy of Module GPIOs (17 x 8 bits) gpio_flag my_module_flag; // Active copy of Template GPIO flags StateBitfield global_state; // Global states (currently Wifi and Mqtt) (8 bits) @@ -169,12 +170,12 @@ char log_data[LOGSZ]; // Logging char web_log[WEB_LOG_SIZE] = {'\0'}; // Web log buffer #ifdef SUPPORT_IF_STATEMENT #include - LinkedList backlog; // Command backlog implemented with LinkedList + LinkedList backlog; // Command backlog implemented with LinkedList #define BACKLOG_EMPTY (backlog.size() == 0) #else - uint8_t backlog_index = 0; // Command backlog index - uint8_t backlog_pointer = 0; // Command backlog pointer - String backlog[MAX_BACKLOG]; // Command backlog buffer + uint8_t backlog_index = 0; // Command backlog index + uint8_t backlog_pointer = 0; // Command backlog pointer + String backlog[MAX_BACKLOG]; // Command backlog buffer #define BACKLOG_EMPTY (backlog_pointer == backlog_index) #endif @@ -809,10 +810,14 @@ void PerformEverySecond(void) if (Settings.tele_period) { tele_period++; - if (tele_period == Settings.tele_period -1) { + // increase time for prepare and document state to ensure TELEPERIOD deliver results + if (tele_period == Settings.tele_period -3 && !prep_called) { + // sensores must be called later if driver switch on e.g. power on deepsleep + XdrvCall(FUNC_PREP_BEFORE_TELEPERIOD); XsnsCall(FUNC_PREP_BEFORE_TELEPERIOD); + prep_called = true; } - if (tele_period >= Settings.tele_period) { + if (tele_period >= Settings.tele_period && prep_called) { tele_period = 0; MqttPublishTeleState(); @@ -824,6 +829,8 @@ void PerformEverySecond(void) RulesTeleperiod(); // Allow rule based HA messages #endif // USE_RULES } + prep_called = true; + XdrvCall(FUNC_AFTER_TELEPERIOD); } } diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 0bb460800..92ae9fd19 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -206,6 +206,7 @@ enum UserSelectablePins { GPIO_DDSU666_RX, // DDSU666 Serial interface GPIO_SM2135_CLK, // SM2135 Clk GPIO_SM2135_DAT, // SM2135 Dat + GPIO_DEEPSLEEP, // Kill switch for deepsleep GPIO_SENSOR_END }; // Programmer selectable GPIO functionality @@ -283,6 +284,7 @@ const char kSensorNames[] PROGMEM = D_SENSOR_DDS2382_TX "|" D_SENSOR_DDS2382_RX "|" D_SENSOR_DDSU666_TX "|" D_SENSOR_DDSU666_RX "|" D_SENSOR_SM2135_CLK "|" D_SENSOR_SM2135_DAT "|" + D_SENSOR_DEEPSLEEP "|" ; const char kSensorNamesFixed[] PROGMEM = @@ -738,6 +740,9 @@ const uint8_t kGpioNiceList[] PROGMEM = { GPIO_A4988_MS2, // A4988 microstep pin2 GPIO_A4988_MS3, // A4988 microstep pin3 #endif +#ifdef USE_DEEPSLEEP + GPIO_DEEPSLEEP +#endif }; const uint8_t kModuleNiceList[] PROGMEM = { diff --git a/sonoff/xdrv_29_deepsleep.ino b/sonoff/xdrv_29_deepsleep.ino new file mode 100644 index 000000000..d48ffb31a --- /dev/null +++ b/sonoff/xdrv_29_deepsleep.ino @@ -0,0 +1,160 @@ +/* + xdrv_29_deepsleep.ino - DeepSleep support for Sonoff-Tasmota + + Copyright (C) 2019 Stefan Bode + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_DEEPSLEEP +/*********************************************************************************************\ + * DeepSleep Support +\*********************************************************************************************/ + +#define XDRV_29 29 + +#define MAX_DEEPSLEEP_CYCLE 3600 // Maximum time for a deepsleep +#define MIN_DEEPSLEEP_TIME 5 + +#define D_PRFX_DEEPSLEEP "DeepSleep" +#define D_CMND_DEEPSLEEP_TIME "Time" + +const char kDeepsleepCommands[] PROGMEM = D_PRFX_DEEPSLEEP "|" + D_CMND_DEEPSLEEP_TIME ; + +void (* const DeepsleepCommand[])(void) PROGMEM = { + &CmndDeepsleepTime }; + +const char JSON_DEEPSLEEP[] PROGMEM = "\"" D_PRFX_DEEPSLEEP "%d\":{\"Time\":%d}"; + +void DeepSleepInit(void) +{ + if (pin[GPIO_DEEPSLEEP] < 99) { + if (!digitalRead(pin[GPIO_DEEPSLEEP])) { + RtcSettings.ultradeepsleep = 0; + } + } + if ((RtcSettings.ultradeepsleep > MAX_DEEPSLEEP_CYCLE) && (RtcSettings.ultradeepsleep < 1700000000)) { + RtcSettings.ultradeepsleep = RtcSettings.ultradeepsleep - MAX_DEEPSLEEP_CYCLE; + RtcReboot.fast_reboot_count = 0; + RtcRebootSave(); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Remain DeepSleep %d"), RtcSettings.ultradeepsleep); + RtcSettingsSave(); + ESP.deepSleep(100 * RtcSettings.deepsleep_slip * (MAX_DEEPSLEEP_CYCLE < RtcSettings.ultradeepsleep ? MAX_DEEPSLEEP_CYCLE : RtcSettings.ultradeepsleep), WAKE_RF_DEFAULT); + yield(); + } + RtcSettings.ultradeepsleep = 0; +} + +void CheckForDeepsleep(void) +{ + uint8_t disable_deepsleep_switch = 0; + if (pin[GPIO_DEEPSLEEP] < 99) { + disable_deepsleep_switch = !digitalRead(pin[GPIO_DEEPSLEEP]); + } + // new function AFTER_TELEPERIOD can take some time therefore <2 + if ((Settings.deepsleep > 10) && (Settings.deepsleep < 4294967295) && !disable_deepsleep_switch && (tele_period < 2) && prep_called) { + SettingsSaveAll(); + Response_P(S_OFFLINE); + MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic + yield(); + // deepsleep_slip is ideally 10.000 == 100% + // typically the device has up to 4% slip. Anything else is a wrong setting in the deepsleep_slip + // therefore all values >110% or <90% will be resetted to 100% to avoid crazy sleep times. + // This should normally never executed, but can happen an manual wakeup and problems during wakeup + if ((RtcSettings.nextwakeup == 0) || (RtcSettings.deepsleep_slip < 9000) || (RtcSettings.deepsleep_slip > 11000) || (RtcSettings.nextwakeup > (UtcTime() + Settings.deepsleep))) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Reset wrong settings wakeup: %ld, slip %ld"), RtcSettings.nextwakeup, RtcSettings.deepsleep_slip ); + RtcSettings.nextwakeup = 0; + RtcSettings.deepsleep_slip = 10000; +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("new settings wakeup: %ld, slip %ld"), RtcSettings.nextwakeup, RtcSettings.deepsleep_slip ); + } + // timeslip in 0.1 seconds between the real wakeup and the calculated wakeup + // because deepsleep is in second and timeslip in 0.1 sec the compare always check if the slip is in the 10% range + int16_t timeslip = (int16_t)(RtcSettings.nextwakeup+millis()/1000-UtcTime())*10; +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: Timeslip 0.1 sec:? %d < %d < %ld"), -Settings.deepsleep, timeslip, Settings.deepsleep ); + //allow 10% of deepsleep error to count as valid deepsleep; expecting 3-4% + // if more then 10% timeslip = 0 == non valid wakeup; maybe manual + timeslip = (timeslip < -(int32_t)Settings.deepsleep) ? 0 : (timeslip > (int32_t)Settings.deepsleep) ? 0 : 1; +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: Normal deepsleep? %d"), timeslip ); + if (timeslip) { + RtcSettings.deepsleep_slip = (Settings.deepsleep + RtcSettings.nextwakeup-UtcTime()) * RtcSettings.deepsleep_slip / (Settings.deepsleep - (millis() / 1000)); + //Avoid crazy numbers. Again maximum 10% deviation. +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: %% calculate drift %ld"), RtcSettings.deepsleep_slip ); + RtcSettings.deepsleep_slip = tmin(tmax(RtcSettings.deepsleep_slip, 9000),11000); + +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: %% new drift %ld"), RtcSettings.deepsleep_slip ); + RtcSettings.nextwakeup += Settings.deepsleep; + } + // it may happen that wakeup in just <5 seconds in future + // in this case also add deepsleep to nextwakeup + if (RtcSettings.nextwakeup <= (UtcTime() - MIN_DEEPSLEEP_TIME)) { + // ensure nextwakeup is at least in the future + RtcSettings.nextwakeup += (((UtcTime() + MIN_DEEPSLEEP_TIME - RtcSettings.nextwakeup) / Settings.deepsleep) + 1) * Settings.deepsleep; + } + Response_P(PSTR("%d"), RtcSettings.nextwakeup); + MqttPublishPrefixTopic_P(TELE, PSTR(D_DOMOTICZ_UPDATE_TIMER), false); // Offline or remove previous retained topic + yield(); + MqttDisconnect(); + String dt = GetDT(RtcSettings.nextwakeup + LocalTime() - UtcTime()); // 2017-03-07T11:08:02 +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: Next wakeup %s"), (char*)dt.c_str()); + //limit sleeptime to MAX_DEEPSLEEP_CYCLE + //uint32_t sleeptime = MAX_DEEPSLEEP_CYCLE < (RtcSettings.nextwakeup - UtcTime()) ? (uint32_t)MAX_DEEPSLEEP_CYCLE : RtcSettings.nextwakeup - UtcTime(); + uint32_t sleeptime = tmin((uint32_t)MAX_DEEPSLEEP_CYCLE , RtcSettings.nextwakeup - UtcTime()); + + RtcSettings.ultradeepsleep = RtcSettings.nextwakeup - UtcTime(); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: Sleeptime %d sec, deepsleep_slip %ld"), sleeptime, RtcSettings.deepsleep_slip); + RtcSettingsSave(); + ESP.deepSleep(100 * RtcSettings.deepsleep_slip * sleeptime); + yield(); + } + prep_called = false; +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void CmndDeepsleepTime(void) +{ +// if ((XdrvMailbox.payload == 0) || ((XdrvMailbox.payload > 10) && (XdrvMailbox.payload < 4294967295))) { + if ((XdrvMailbox.payload == 0) || ((XdrvMailbox.payload > 10) && (XdrvMailbox.payload < (24 * 60 * 60)))) { // Allow max 24 hours sleep + Settings.deepsleep = XdrvMailbox.payload; + RtcSettings.nextwakeup = 0; + } + Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, Settings.deepsleep); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv29(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_AFTER_TELEPERIOD: + CheckForDeepsleep(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kDeepsleepCommands, DeepsleepCommand); + break; + case FUNC_PRE_INIT: + DeepSleepInit(); + break; + } + return result; +} + +#endif //USE_DEEPSLEEP \ No newline at end of file