From f4b0e9fe5b19f8cc3bf030496e934e175b80c0e8 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Thu, 21 Nov 2019 17:28:10 +0300 Subject: [PATCH 1/7] relay: fix dummy relays overwriting real ones --- code/espurna/relay.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index bae0f3df..d8138ed0 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -44,9 +44,9 @@ typedef struct { Ticker pulseTicker; // Holds the pulse back timer } relay_t; + std::vector _relays; bool _relayRecursive = false; -Ticker _relaySaveTicker; uint8_t _relayDummy = DUMMY_RELAY_COUNT; unsigned long _relay_flood_window = (1000 * RELAY_FLOOD_WINDOW); @@ -248,7 +248,7 @@ void _relayProviderStatus(unsigned char id, bool status) { lightUpdate(true, true); return; - + } #endif @@ -1330,11 +1330,11 @@ void _relayLoop() { // 8 channels. This behaviour will be recovered with v2. void relaySetupDummy(unsigned char size, bool reconfigure) { - size = constrain(size, 0, RELAY_SAVE_MASK_MAX); + size = constrain(size + _relays.size(), _relays.size(), RELAY_SAVE_MASK_MAX); if (size == _relays.size()) return; _relayDummy = size; - _relays.assign(size, { + _relays.insert(_relays.end(), size, { GPIO_NONE, RELAY_TYPE_NORMAL, GPIO_NONE }); From 9f4329dc1a179e644ffbf086a771bb80a4890921 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Thu, 21 Nov 2019 17:30:31 +0300 Subject: [PATCH 2/7] relay: replace eeprom 8bit with 32bit bitmask stored in settings - mask is defined as (relay# status << number) - transparently handle base-2 and base-10 numbers - if the relay mode requires us to save the mask, it will be saved as base-2 --- code/espurna/alexa.ino | 1 + code/espurna/button.ino | 1 + code/espurna/config/general.h | 5 --- code/espurna/config/prototypes.h | 30 ------------- code/espurna/domoticz.ino | 2 + code/espurna/espurna.ino | 2 + code/espurna/ir.ino | 2 + code/espurna/led.ino | 1 + code/espurna/relay.h | 38 ++++++++++++++++ code/espurna/relay.ino | 74 ++++++++++++++++++++------------ code/espurna/rfbridge.ino | 2 + code/espurna/rpnrules.ino | 4 +- code/espurna/scheduler.ino | 2 + code/espurna/sensor.ino | 1 + code/espurna/terminal.ino | 6 ++- code/espurna/thermostat.ino | 2 + code/espurna/tuya.ino | 3 ++ code/espurna/utils.h | 56 ++++++++++++++++++++++++ code/espurna/utils.ino | 69 +++++++++++++++++++++++++---- code/espurna/web.ino | 2 + 20 files changed, 229 insertions(+), 74 deletions(-) create mode 100644 code/espurna/relay.h create mode 100644 code/espurna/utils.h diff --git a/code/espurna/alexa.ino b/code/espurna/alexa.ino index 94ce0150..fc62d666 100644 --- a/code/espurna/alexa.ino +++ b/code/espurna/alexa.ino @@ -8,6 +8,7 @@ Copyright (C) 2016-2019 by Xose Pérez #if ALEXA_SUPPORT +#include "relay.h" #include "broker.h" #include diff --git a/code/espurna/button.ino b/code/espurna/button.ino index 07045fdc..e23413a1 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.ino @@ -15,6 +15,7 @@ Copyright (C) 2016-2019 by Xose Pérez #include #include +#include "relay.h" #include "light.h" typedef struct { diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 510f908d..6cc823f7 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -429,11 +429,6 @@ #define RELAY_MQTT_TOGGLE "2" #endif -// TODO Only single EEPROM address is used to store state, which is 1 byte -// Relay status is stored using bitfield. -// This means that, atm, we are only storing the status of the first 8 relays. -#define RELAY_SAVE_MASK_MAX 8 - // ----------------------------------------------------------------------------- // WIFI // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index bbccefb6..84cfd457 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -250,36 +250,6 @@ typedef struct { int16_t rssi; } packet_t; -// ----------------------------------------------------------------------------- -// Relay -// ----------------------------------------------------------------------------- -#include - -enum class RelayStatus : unsigned char { - OFF = 0, - ON = 1, - TOGGLE = 2, - UNKNOWN = 0xFF -}; - -RelayStatus relayParsePayload(const char * payload); - -bool relayStatus(unsigned char id, bool status, bool report, bool group_report); -bool relayStatus(unsigned char id, bool status); -bool relayStatus(unsigned char id); - -void relayToggle(unsigned char id, bool report, bool group_report); -void relayToggle(unsigned char id); - -unsigned char relayCount(); - -const String& relayPayloadOn(); -const String& relayPayloadOff(); -const String& relayPayloadToggle(); -const char* relayPayload(RelayStatus status); - -void relaySetupDummy(unsigned char size, bool reconfigure = false); - // ----------------------------------------------------------------------------- // Settings // ----------------------------------------------------------------------------- diff --git a/code/espurna/domoticz.ino b/code/espurna/domoticz.ino index f54a3a9c..2f516479 100644 --- a/code/espurna/domoticz.ino +++ b/code/espurna/domoticz.ino @@ -8,7 +8,9 @@ Copyright (C) 2016-2019 by Xose Pérez #if DOMOTICZ_SUPPORT +#include "relay.h" #include "broker.h" + #include bool _dcz_enabled = false; diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 0206acc3..c5587383 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -22,6 +22,8 @@ along with this program. If not, see . #include "config/all.h" #include +#include "utils.h" +#include "relay.h" #include "broker.h" #include "tuya.h" #include "libs/HeapStats.h" diff --git a/code/espurna/ir.ino b/code/espurna/ir.ino index 780dbe7a..ccd6237a 100644 --- a/code/espurna/ir.ino +++ b/code/espurna/ir.ino @@ -48,6 +48,8 @@ Raw messages: #if IR_SUPPORT +#include "relay.h" + #include #if defined(IR_RX_PIN) diff --git a/code/espurna/led.ino b/code/espurna/led.ino index 3eca7371..d743328b 100644 --- a/code/espurna/led.ino +++ b/code/espurna/led.ino @@ -12,6 +12,7 @@ Copyright (C) 2016-2019 by Xose Pérez #if LED_SUPPORT +#include "relay.h" #include "broker.h" typedef struct { diff --git a/code/espurna/relay.h b/code/espurna/relay.h new file mode 100644 index 00000000..34240735 --- /dev/null +++ b/code/espurna/relay.h @@ -0,0 +1,38 @@ +/* + +RELAY MODULE + +Copyright (C) 2016-2019 by Xose Pérez + +*/ + +#pragma once + +constexpr size_t RELAYS_MAX = 32; + +enum class RelayStatus : unsigned char { + OFF = 0, + ON = 1, + TOGGLE = 2, + UNKNOWN = 0xFF +}; + +RelayStatus relayParsePayload(const char * payload); + +bool relayStatus(unsigned char id, bool status, bool report, bool group_report); +bool relayStatus(unsigned char id, bool status); +bool relayStatus(unsigned char id); + +void relayToggle(unsigned char id, bool report, bool group_report); +void relayToggle(unsigned char id); + +unsigned char relayCount(); + +const String& relayPayloadOn(); +const String& relayPayloadOff(); +const String& relayPayloadToggle(); + +const char* relayPayload(RelayStatus status); + +void relaySetupDummy(unsigned char size, bool reconfigure = false); + diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index d8138ed0..e463f156 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -11,7 +11,9 @@ Copyright (C) 2016-2019 by Xose Pérez #include #include #include +#include +#include "relay.h" #include "broker.h" #include "tuya.h" @@ -390,6 +392,18 @@ uint32_t _relayMaskRtcmem() { return Rtcmem->relay; } +void _relayMaskSettings(const String& string) { + setSetting("relayBootMask", string); +} + +void _relayMaskSettings(uint32_t mask) { + _relayMaskSettings(bitsetToString(mask)); +} + +uint32_t _relayMaskSettings() { + return bitsetFromString(getSetting("relayBootMask")); +} + void relayPulse(unsigned char id) { _relays[id].pulseTicker.detach(); @@ -563,18 +577,16 @@ void relaySync(unsigned char id) { void relaySave(bool eeprom) { - auto mask = std::bitset(0); - - unsigned char count = relayCount(); - if (count > RELAY_SAVE_MASK_MAX) count = RELAY_SAVE_MASK_MAX; + auto mask = std::bitset(0); - for (unsigned int i=0; i < count; ++i) { - mask.set(i, relayStatus(i)); + const unsigned char count = constrain(relayCount(), 0, RELAYS_MAX); + for (unsigned int id = 0; id < count; ++id) { + mask.set(id, relayStatus(id)); } const uint32_t mask_value = mask.to_ulong(); - - DEBUG_MSG_P(PSTR("[RELAY] Setting relay mask: %u\n"), mask_value); + const String mask_string = bitsetToString(mask_value); + DEBUG_MSG_P(PSTR("[RELAY] Setting relay mask: %s\n"), mask_string.c_str()); // Persist only to rtcmem, unless requested to save to the eeprom _relayMaskRtcmem(mask_value); @@ -586,7 +598,7 @@ void relaySave(bool eeprom) { // Nevertheless, we store the value in the EEPROM buffer so it will be written // on the next commit. if (eeprom) { - EEPROMr.write(EEPROM_RELAY_STATUS, mask_value); + _relayMaskSettings(mask_string); // We are actually enqueuing the commit so it will be // executed on the main loop, in case this is called from a system context callback eepromCommit(); @@ -651,6 +663,17 @@ RelayStatus relayParsePayload(const char * payload) { // BACKWARDS COMPATIBILITY void _relayBackwards() { + #if defined(EEPROM_RELAY_STATUS) + { + uint8_t mask = EEPROMr.read(EEPROM_RELAY_STATUS); + if (mask != 0xff) { + _relayMaskSettings(static_cast(mask)); + EEPROMr.write(EEPROM_RELAY_STATUS, 0xff); + eepromCommit(); + } + } + #endif + for (unsigned int i=0; i<_relays.size(); i++) { if (!hasSetting("mqttGroupInv", i)) continue; setSetting("mqttGroupSync", i, getSetting("mqttGroupInv", i)); @@ -668,12 +691,13 @@ void _relayBoot() { if (rtcmemStatus()) { stored_mask = _relayMaskRtcmem(); } else { - stored_mask = EEPROMr.read(EEPROM_RELAY_STATUS); + stored_mask = _relayMaskSettings(); } - DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %u\n"), stored_mask); + const String string_mask(bitsetToString(stored_mask)); + DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %s\n"), string_mask.c_str()); - auto mask = std::bitset(stored_mask); + auto mask = std::bitset(stored_mask); // Walk the relays unsigned char lock; @@ -687,16 +711,12 @@ void _relayBoot() { lock = RELAY_LOCK_DISABLED; switch (boot_mode) { case RELAY_BOOT_SAME: - if (i < 8) { - status = mask.test(i); - } + status = mask.test(i); break; case RELAY_BOOT_TOGGLE: - if (i < 8) { - status = !mask[i]; - mask.flip(i); - trigger_save = true; - } + status = !mask[i]; + mask.flip(i); + trigger_save = true; break; case RELAY_BOOT_LOCKED_ON: status = true; @@ -727,12 +747,12 @@ void _relayBoot() { } + const auto mask_value = mask.to_ulong(); + _relayMaskRtcmem(mask_value); + // Save if there is any relay in the RELAY_BOOT_TOGGLE mode if (trigger_save) { - _relayMaskRtcmem(mask.to_ulong()); - - EEPROMr.write(EEPROM_RELAY_STATUS, mask.to_ulong()); - eepromCommit(); + _relayMaskSettings(mask_value); } _relayRecursive = false; @@ -1325,12 +1345,10 @@ void _relayLoop() { #endif } -// Dummy relays for AI Light, Magic Home LED Controller, H801, Sonoff Dual and Sonoff RF Bridge -// No delay_on or off for these devices to easily allow having more than -// 8 channels. This behaviour will be recovered with v2. +// Dummy relays for virtual light switches, Sonoff Dual, Sonoff RF Bridge and Tuya void relaySetupDummy(unsigned char size, bool reconfigure) { - size = constrain(size + _relays.size(), _relays.size(), RELAY_SAVE_MASK_MAX); + size = constrain(size + _relays.size(), _relays.size(), RELAYS_MAX); if (size == _relays.size()) return; _relayDummy = size; diff --git a/code/espurna/rfbridge.ino b/code/espurna/rfbridge.ino index 4d01cefa..10e321e1 100644 --- a/code/espurna/rfbridge.ino +++ b/code/espurna/rfbridge.ino @@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez #if RF_SUPPORT +#include "relay.h" + #include #include diff --git a/code/espurna/rpnrules.ino b/code/espurna/rpnrules.ino index 96f9b270..5e275f44 100644 --- a/code/espurna/rpnrules.ino +++ b/code/espurna/rpnrules.ino @@ -8,7 +8,9 @@ Copyright (C) 2019 by Xose Pérez #if RPN_RULES_SUPPORT -#include "rpnlib.h" +#include "relay.h" + +#include // ----------------------------------------------------------------------------- // Custom commands diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.ino index b106af44..5dd7e03c 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.ino @@ -9,6 +9,8 @@ Adapted by Xose Pérez #if SCHEDULER_SUPPORT +#include "relay.h" + #include int _sch_restore = 0; diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index 07b228ec..1190feb9 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -8,6 +8,7 @@ Copyright (C) 2016-2019 by Xose Pérez #if SENSOR_SUPPORT +#include "relay.h" #include "broker.h" #include diff --git a/code/espurna/terminal.ino b/code/espurna/terminal.ino index d57818ab..bc460169 100644 --- a/code/espurna/terminal.ino +++ b/code/espurna/terminal.ino @@ -8,12 +8,14 @@ Copyright (C) 2016-2019 by Xose Pérez #if TERMINAL_SUPPORT -#include +#include "utils.h" #include "libs/EmbedisWrap.h" -#include #include "libs/StreamInjector.h" #include "libs/HeapStats.h" +#include +#include + StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE); EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE); diff --git a/code/espurna/thermostat.ino b/code/espurna/thermostat.ino index 4ad5c543..82c212da 100644 --- a/code/espurna/thermostat.ino +++ b/code/espurna/thermostat.ino @@ -8,6 +8,8 @@ Copyright (C) 2017 by Dmitry Blinov #if THERMOSTAT_SUPPORT +#include "relay.h" + #include #include diff --git a/code/espurna/tuya.ino b/code/espurna/tuya.ino index d25ec2c8..39418203 100644 --- a/code/espurna/tuya.ino +++ b/code/espurna/tuya.ino @@ -10,6 +10,9 @@ Copyright (C) 2019 by Maxim Prokhorov #if TUYA_SUPPORT +#include "relay.h" +#include "light.h" + #include #include #include diff --git a/code/espurna/utils.h b/code/espurna/utils.h new file mode 100644 index 00000000..c6990087 --- /dev/null +++ b/code/espurna/utils.h @@ -0,0 +1,56 @@ +/* + +UTILS MODULE + +Copyright (C) 2017-2019 by Xose Pérez + +*/ + +#pragma once + +extern "C" uint32_t _SPIFFS_start; +extern "C" uint32_t _SPIFFS_end; + +String getIdentifier(); +void setDefaultHostname(); + +void setBoardName(); +String getBoardName(); +String getAdminPass(); + +const String& getCoreVersion(); +const String& getCoreRevision(); + +unsigned char getHeartbeatMode(); +unsigned char getHeartbeatInterval(); +void heartbeat(); + +String getEspurnaModules(); +String getEspurnaOTAModules(); +String getEspurnaSensors(); + +String getEspurnaWebUI(); + +String buildTime(); +unsigned long getUptime(); +bool haveRelaysOrSensors(); + +void infoMemory(const char * name, unsigned int total_memory, unsigned int free_memory); +void info(); + +bool sslCheckFingerPrint(const char * fingerprint); +bool sslFingerPrintArray(const char * fingerprint, unsigned char * bytearray); +bool sslFingerPrintChar(const char * fingerprint, char * destination); + +bool eraseSDKConfig(); + +char * ltrim(char * s); +char * strnstr(const char * buffer, const char * token, size_t n); +bool isNumber(const char * s); + +void nice_delay(unsigned long ms); + +double roundTo(double num, unsigned char positions); + +uint32_t bitsetFromString(const String& string); +String bitsetToString(uint32_t bitset); diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index ea627187..be30190a 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -6,9 +6,11 @@ Copyright (C) 2017-2019 by Xose Pérez */ +#include "utils.h" +#include "libs/HeapStats.h" + #include #include -#include "libs/HeapStats.h" String getIdentifier() { char buffer[20]; @@ -64,7 +66,7 @@ const String& getCoreRevision() { #ifdef ARDUINO_ESP8266_GIT_VER revision = String(ARDUINO_ESP8266_GIT_VER, 16); #else - revision = ""; + revision = "(unspecified)"; #endif } return revision; @@ -132,6 +134,22 @@ bool haveRelaysOrSensors() { return result; } +// TODO: force getSetting return type to handle settings +uint32_t u32fromString(const String& string, int base = 10) { + + const char *ptr = string.c_str(); + char *value_endptr = nullptr; + + // invalidate the whole string when invalid chars are detected + const auto value = strtoul(ptr, &value_endptr, base); + if (value_endptr == ptr || value_endptr[0] != '\0') { + return 0; + } + + return value; + +} + // ----------------------------------------------------------------------------- // Heartbeat helper // ----------------------------------------------------------------------------- @@ -192,12 +210,7 @@ namespace Heartbeat { return defaultValue(); } - // invalidate the whole string when invalid chars are detected - char *value_endptr = nullptr; - const auto value = strtoul(cfg.c_str(), &value_endptr, 10); - if (value_endptr == cfg.c_str() || value_endptr[0] != '\0') { - return defaultValue(); - } + const auto value = u32fromString(cfg); // because we start shifting from 1, we could use the // first bit as a flag to enable all of the messages @@ -677,3 +690,43 @@ char* strnstr(const char* buffer, const char* token, size_t n) { return nullptr; } + +// Note: +// - when using standard base-2 literal syntax, parse that +// to keep backwards compatibility +// - otherwise, fallback to base-10 numbers +uint32_t bitsetFromString(const String& string) { + if (!string.length()) { + return 0; + } + + if (string.startsWith("0b") && (string.length() > 2)) { + return u32fromString(string.substring(2), 2); + } + + return u32fromString(string); +} + +// Note: +// - bitset::to_string() will return std::string +// - itoa accepts int, so it will cut the sign bit +String bitsetToString(uint32_t value) { + String result; + result.reserve(34); + result += "0b"; + + const uint32_t _value { value }; + size_t bits = 0; + + do { + value >>= 1; + bits++; + } while (value); + + int bit = bits - 1; + do { + result += ((_value & (1 << bit)) ? '1' : '0'); + } while (--bit >= 0); + + return result; +} diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 3c80fd2c..3620a721 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez #if WEB_SUPPORT +#include "utils.h" + #include #include #include From e7fc5f337b980895d0f7084a28c3fbe5b6ae9f82 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 22 Nov 2019 23:10:12 +0300 Subject: [PATCH 3/7] relay: rework mask handling to use generic u32<->string conversion --- code/espurna/relay.h | 34 +++++++++++++++++++ code/espurna/relay.ino | 70 ++++++++++++++++++++++---------------- code/espurna/utils.h | 5 +-- code/espurna/utils.ino | 76 +++++++++++++++++++----------------------- 4 files changed, 113 insertions(+), 72 deletions(-) diff --git a/code/espurna/relay.h b/code/espurna/relay.h index 34240735..b951ee6f 100644 --- a/code/espurna/relay.h +++ b/code/espurna/relay.h @@ -8,6 +8,9 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include +#include "utils.h" + constexpr size_t RELAYS_MAX = 32; enum class RelayStatus : unsigned char { @@ -17,6 +20,37 @@ enum class RelayStatus : unsigned char { UNKNOWN = 0xFF }; +struct RelayMask { + + explicit RelayMask(const String& string) : + as_string(string), + as_u32(u32fromString(string)) + {} + + explicit RelayMask(String&& string) : + as_string(std::move(string)), + as_u32(u32fromString(as_string)) + {} + + explicit RelayMask(uint32_t value) : + as_string(std::move(u32toString(value, 2))), + as_u32(value) + {} + + explicit RelayMask(std::bitset bitset) : + RelayMask(bitset.to_ulong()) + {} + + RelayMask(String&& string, uint32_t value) : + as_string(std::move(string)), + as_u32(value) + {} + + const String as_string; + uint32_t as_u32; + +}; + RelayStatus relayParsePayload(const char * payload); bool relayStatus(unsigned char id, bool status, bool report, bool group_report); diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index e463f156..d4ee177e 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -384,26 +384,42 @@ void setSpeed(unsigned char speed) { // RELAY // ----------------------------------------------------------------------------- -void _relayMaskRtcmem(uint32_t mask) { +// State persistance persistance + +RelayMask INLINE _relayMaskRtcmem() { + return RelayMask(Rtcmem->relay); +} + +void INLINE _relayMaskRtcmem(uint32_t mask) { Rtcmem->relay = mask; } -uint32_t _relayMaskRtcmem() { - return Rtcmem->relay; +void INLINE _relayMaskRtcmem(const RelayMask& mask) { + _relayMaskRtcmem(mask.as_u32); +} + +void INLINE _relayMaskRtcmem(const std::bitset& bitset) { + _relayMaskRtcmem(bitset.to_ulong()); +} + +RelayMask INLINE _relayMaskSettings() { + return RelayMask(getSetting("relayBootMask")); } -void _relayMaskSettings(const String& string) { - setSetting("relayBootMask", string); +void INLINE _relayMaskSettings(uint32_t mask) { + setSetting("relayBootMask", u32toString(mask, 2)); } -void _relayMaskSettings(uint32_t mask) { - _relayMaskSettings(bitsetToString(mask)); +void INLINE _relayMaskSettings(const RelayMask& mask) { + setSetting("relayBootMask", mask.as_string); } -uint32_t _relayMaskSettings() { - return bitsetFromString(getSetting("relayBootMask")); +void INLINE _relayMaskSettings(const std::bitset& bitset) { + _relayMaskSettings(bitset.to_ulong()); } +// Pulse timers (timer after ON or OFF event) + void relayPulse(unsigned char id) { _relays[id].pulseTicker.detach(); @@ -426,6 +442,8 @@ void relayPulse(unsigned char id) { } +// General relay status control + bool relayStatus(unsigned char id, bool status, bool report, bool group_report) { if (id >= _relays.size()) return false; @@ -577,19 +595,18 @@ void relaySync(unsigned char id) { void relaySave(bool eeprom) { - auto mask = std::bitset(0); - const unsigned char count = constrain(relayCount(), 0, RELAYS_MAX); + + auto statuses = std::bitset(0); for (unsigned int id = 0; id < count; ++id) { - mask.set(id, relayStatus(id)); + statuses.set(id, relayStatus(id)); } - const uint32_t mask_value = mask.to_ulong(); - const String mask_string = bitsetToString(mask_value); - DEBUG_MSG_P(PSTR("[RELAY] Setting relay mask: %s\n"), mask_string.c_str()); + const RelayMask mask(statuses); + DEBUG_MSG_P(PSTR("[RELAY] Setting relay mask: %s\n"), mask.as_string.c_str()); // Persist only to rtcmem, unless requested to save to the eeprom - _relayMaskRtcmem(mask_value); + _relayMaskRtcmem(mask); // The 'eeprom' flag controls whether we are commiting this change or not. // It is useful to set it to 'false' if the relay change triggering the @@ -598,7 +615,7 @@ void relaySave(bool eeprom) { // Nevertheless, we store the value in the EEPROM buffer so it will be written // on the next commit. if (eeprom) { - _relayMaskSettings(mask_string); + _relayMaskSettings(mask); // We are actually enqueuing the commit so it will be // executed on the main loop, in case this is called from a system context callback eepromCommit(); @@ -686,18 +703,14 @@ void _relayBoot() { _relayRecursive = true; bool trigger_save = false; - uint32_t stored_mask = 0; - if (rtcmemStatus()) { - stored_mask = _relayMaskRtcmem(); - } else { - stored_mask = _relayMaskSettings(); - } + const auto stored_mask = rtcmemStatus() + ? _relayMaskRtcmem() + : _relayMaskSettings(); - const String string_mask(bitsetToString(stored_mask)); - DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %s\n"), string_mask.c_str()); + DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %s\n"), stored_mask.as_string.c_str()); - auto mask = std::bitset(stored_mask); + auto mask = std::bitset(stored_mask.as_u32); // Walk the relays unsigned char lock; @@ -747,12 +760,11 @@ void _relayBoot() { } - const auto mask_value = mask.to_ulong(); - _relayMaskRtcmem(mask_value); + _relayMaskRtcmem(mask); // Save if there is any relay in the RELAY_BOOT_TOGGLE mode if (trigger_save) { - _relayMaskSettings(mask_value); + _relayMaskSettings(mask); } _relayRecursive = false; diff --git a/code/espurna/utils.h b/code/espurna/utils.h index c6990087..0a8e5e6d 100644 --- a/code/espurna/utils.h +++ b/code/espurna/utils.h @@ -52,5 +52,6 @@ void nice_delay(unsigned long ms); double roundTo(double num, unsigned char positions); -uint32_t bitsetFromString(const String& string); -String bitsetToString(uint32_t bitset); +uint32_t u32fromString(const String& string, int base); +uint32_t u32fromString(const String& string); +String u32toString(uint32_t bitset, int base); diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index be30190a..b2461713 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -134,22 +134,6 @@ bool haveRelaysOrSensors() { return result; } -// TODO: force getSetting return type to handle settings -uint32_t u32fromString(const String& string, int base = 10) { - - const char *ptr = string.c_str(); - char *value_endptr = nullptr; - - // invalidate the whole string when invalid chars are detected - const auto value = strtoul(ptr, &value_endptr, base); - if (value_endptr == ptr || value_endptr[0] != '\0') { - return 0; - } - - return value; - -} - // ----------------------------------------------------------------------------- // Heartbeat helper // ----------------------------------------------------------------------------- @@ -691,42 +675,52 @@ char* strnstr(const char* buffer, const char* token, size_t n) { return nullptr; } -// Note: -// - when using standard base-2 literal syntax, parse that -// to keep backwards compatibility -// - otherwise, fallback to base-10 numbers -uint32_t bitsetFromString(const String& string) { +// TODO: force getSetting return type to handle settings +uint32_t u32fromString(const String& string, int base) { + + const char *ptr = string.c_str(); + char *value_endptr = nullptr; + + // invalidate the whole string when invalid chars are detected + const auto value = strtoul(ptr, &value_endptr, base); + if (value_endptr == ptr || value_endptr[0] != '\0') { + return 0; + } + + return value; + +} + +uint32_t u32fromString(const String& string) { if (!string.length()) { return 0; } - if (string.startsWith("0b") && (string.length() > 2)) { - return u32fromString(string.substring(2), 2); + int base = 10; + if (string.length() > 2) { + if (string.startsWith("0b")) { + base = 2; + } else if (string.startsWith("0o")) { + base = 8; + } else if (string.startsWith("0x")) { + base = 16; + } else { + return 0; + } + return u32fromString(string.substring(2), base); } - return u32fromString(string); + return u32fromString(string, base); } -// Note: -// - bitset::to_string() will return std::string -// - itoa accepts int, so it will cut the sign bit -String bitsetToString(uint32_t value) { +String u32toString(uint32_t value, int base) { String result; - result.reserve(34); + result.reserve(32 + 2); result += "0b"; - const uint32_t _value { value }; - size_t bits = 0; - - do { - value >>= 1; - bits++; - } while (value); - - int bit = bits - 1; - do { - result += ((_value & (1 << bit)) ? '1' : '0'); - } while (--bit >= 0); + char buffer[33] = {0}; + ultoa(value, buffer, base); + result += buffer; return result; } From 5e74adb23e28e07f9c42c0e63f71c054dd643a0b Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 22 Nov 2019 23:11:18 +0300 Subject: [PATCH 4/7] relay: proxy header defaults through methods --- code/espurna/relay.ino | 88 +++++++++++++++++++------------------ code/espurna/relay_config.h | 73 ++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 42 deletions(-) create mode 100644 code/espurna/relay_config.h diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index d4ee177e..38ea9f80 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -17,7 +17,25 @@ Copyright (C) 2016-2019 by Xose Pérez #include "broker.h" #include "tuya.h" -typedef struct { +#include "relay_config.h" + +struct relay_t { + + // Default to dummy (virtual) relay configuration + + relay_t() : + pin(GPIO_NONE), + type(GPIO_NONE), + reset_pin(GPIO_NONE) + {} + + // ... unless there are pre-configured values + + relay_t(unsigned char id) : + pin(_relayPin(id)), + type(_relayType(id)), + reset_pin(_relayResetPin(id)) + {} // Configuration variables @@ -45,7 +63,7 @@ typedef struct { Ticker pulseTicker; // Holds the pulse back timer -} relay_t; +}; std::vector _relays; bool _relayRecursive = false; @@ -775,32 +793,6 @@ void _relayBoot() { } -constexpr const unsigned long _relayDelayOn(unsigned char index) { - return ( - (index == 0) ? RELAY1_DELAY_ON : - (index == 1) ? RELAY2_DELAY_ON : - (index == 2) ? RELAY3_DELAY_ON : - (index == 3) ? RELAY4_DELAY_ON : - (index == 4) ? RELAY5_DELAY_ON : - (index == 5) ? RELAY6_DELAY_ON : - (index == 6) ? RELAY7_DELAY_ON : - (index == 7) ? RELAY8_DELAY_ON : 0 - ); -} - -constexpr const unsigned long _relayDelayOff(unsigned char index) { - return ( - (index == 0) ? RELAY1_DELAY_OFF : - (index == 1) ? RELAY2_DELAY_OFF : - (index == 2) ? RELAY3_DELAY_OFF : - (index == 3) ? RELAY4_DELAY_OFF : - (index == 4) ? RELAY5_DELAY_OFF : - (index == 5) ? RELAY6_DELAY_OFF : - (index == 6) ? RELAY7_DELAY_OFF : - (index == 7) ? RELAY8_DELAY_OFF : 0 - ); -} - void _relayConfigure() { for (unsigned int i=0; i<_relays.size(); i++) { _relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt(); @@ -1362,11 +1354,9 @@ void relaySetupDummy(unsigned char size, bool reconfigure) { size = constrain(size + _relays.size(), _relays.size(), RELAYS_MAX); if (size == _relays.size()) return; - _relayDummy = size; - _relays.insert(_relays.end(), size, { - GPIO_NONE, RELAY_TYPE_NORMAL, GPIO_NONE - }); + _relayDummy = size; + _relays.resize(size); if (reconfigure) { _relayConfigure(); @@ -1378,34 +1368,48 @@ void relaySetupDummy(unsigned char size, bool reconfigure) { } -void relaySetup() { +void _relaySetupAdhoc() { + + size_t relays = 0; - // Ad-hoc relays #if RELAY1_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN }); + ++relays; #endif #if RELAY2_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN }); + ++relays; #endif #if RELAY3_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN }); + ++relays; #endif #if RELAY4_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN }); + ++relays; #endif #if RELAY5_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN }); + ++relays; #endif #if RELAY6_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN }); + ++relays; #endif #if RELAY7_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN }); + ++relays; #endif #if RELAY8_PIN != GPIO_NONE - _relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN }); + ++relays; #endif + _relays.reserve(relays); + for (unsigned char id = 0; id < relays; ++id) { + _relays.emplace_back(id); + } + +} + +void relaySetup() { + + // Ad-hoc relays + _relaySetupAdhoc(); + + // Dummy (virtual) relays relaySetupDummy(getSetting("relayDummy", DUMMY_RELAY_COUNT).toInt()); _relaySetupProvider(); diff --git a/code/espurna/relay_config.h b/code/espurna/relay_config.h new file mode 100644 index 00000000..5254b5da --- /dev/null +++ b/code/espurna/relay_config.h @@ -0,0 +1,73 @@ +/* + +RELAY MODULE + +*/ + +#pragma once + +constexpr const unsigned long _relayDelayOn(unsigned char index) { + return ( + (index == 0) ? RELAY1_DELAY_ON : + (index == 1) ? RELAY2_DELAY_ON : + (index == 2) ? RELAY3_DELAY_ON : + (index == 3) ? RELAY4_DELAY_ON : + (index == 4) ? RELAY5_DELAY_ON : + (index == 5) ? RELAY6_DELAY_ON : + (index == 6) ? RELAY7_DELAY_ON : + (index == 7) ? RELAY8_DELAY_ON : 0 + ); +} + +constexpr const unsigned long _relayDelayOff(unsigned char index) { + return ( + (index == 0) ? RELAY1_DELAY_OFF : + (index == 1) ? RELAY2_DELAY_OFF : + (index == 2) ? RELAY3_DELAY_OFF : + (index == 3) ? RELAY4_DELAY_OFF : + (index == 4) ? RELAY5_DELAY_OFF : + (index == 5) ? RELAY6_DELAY_OFF : + (index == 6) ? RELAY7_DELAY_OFF : + (index == 7) ? RELAY8_DELAY_OFF : 0 + ); +} + +constexpr const unsigned char _relayPin(unsigned char index) { + return ( + (index == 0) ? RELAY1_PIN : + (index == 1) ? RELAY2_PIN : + (index == 2) ? RELAY3_PIN : + (index == 3) ? RELAY4_PIN : + (index == 4) ? RELAY5_PIN : + (index == 5) ? RELAY6_PIN : + (index == 6) ? RELAY7_PIN : + (index == 7) ? RELAY8_PIN : GPIO_NONE + ); +} + +constexpr const unsigned char _relayType(unsigned char index) { + return ( + (index == 0) ? RELAY1_TYPE : + (index == 1) ? RELAY2_TYPE : + (index == 2) ? RELAY3_TYPE : + (index == 3) ? RELAY4_TYPE : + (index == 4) ? RELAY5_TYPE : + (index == 5) ? RELAY6_TYPE : + (index == 6) ? RELAY7_TYPE : + (index == 7) ? RELAY8_TYPE : RELAY_TYPE_NORMAL + ); +} + +constexpr const unsigned char _relayResetPin(unsigned char index) { + return ( + (index == 0) ? RELAY1_RESET_PIN : + (index == 1) ? RELAY2_RESET_PIN : + (index == 2) ? RELAY3_RESET_PIN : + (index == 3) ? RELAY4_RESET_PIN : + (index == 4) ? RELAY5_RESET_PIN : + (index == 5) ? RELAY6_RESET_PIN : + (index == 6) ? RELAY7_RESET_PIN : + (index == 7) ? RELAY8_RESET_PIN : GPIO_NONE + ); +} + From 83f49e30ea31e42de169f55c548f7dbade6a75f5 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Sat, 23 Nov 2019 00:17:30 +0300 Subject: [PATCH 5/7] relay: write boot mask later, add relay_t defaults --- code/espurna/relay.ino | 82 +++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 38ea9f80..38ee8cf7 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -23,45 +23,60 @@ struct relay_t { // Default to dummy (virtual) relay configuration + relay_t(unsigned char pin, unsigned char type, unsigned char reset_pin) : + pin(pin), + type(type), + reset_pin(reset_pin), + delay_on(0), + delay_off(0), + pulse(RELAY_PULSE_NONE), + pulse_ms(0), + current_status(false), + target_status(false), + lock(RELAY_LOCK_DISABLED), + fw_start(0), + fw_count(0), + change_start(0), + change_delay(0), + report(false), + group_report(false) + {} + relay_t() : - pin(GPIO_NONE), - type(GPIO_NONE), - reset_pin(GPIO_NONE) + relay_t(GPIO_NONE, RELAY_TYPE_NORMAL, GPIO_NONE) {} // ... unless there are pre-configured values relay_t(unsigned char id) : - pin(_relayPin(id)), - type(_relayType(id)), - reset_pin(_relayResetPin(id)) + relay_t(_relayPin(id), _relayType(id), _relayResetPin(id)) {} // Configuration variables - unsigned char pin; // GPIO pin for the relay - unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE - unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED - unsigned long delay_on; // Delay to turn relay ON - unsigned long delay_off; // Delay to turn relay OFF - unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON - unsigned long pulse_ms; // Pulse length in millis + unsigned char pin; // GPIO pin for the relay + unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE + unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED + unsigned long delay_on; // Delay to turn relay ON + unsigned long delay_off; // Delay to turn relay OFF + unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON + unsigned long pulse_ms; // Pulse length in millis // Status variables - bool current_status; // Holds the current (physical) status of the relay - bool target_status; // Holds the target status - unsigned char lock; // Holds the value of target status, that cannot be changed afterwards. (0 for false, 1 for true, 2 to disable) - unsigned long fw_start; // Flood window start time - unsigned char fw_count; // Number of changes within the current flood window - unsigned long change_start; // Time when relay was scheduled to change - unsigned long change_delay; // Delay until the next change - bool report; // Whether to report to own topic - bool group_report; // Whether to report to group topic + bool current_status; // Holds the current (physical) status of the relay + bool target_status; // Holds the target status + unsigned char lock; // Holds the value of target status, that cannot be changed afterwards. (0 for false, 1 for true, 2 to disable) + unsigned long fw_start; // Flood window start time + unsigned char fw_count; // Number of changes within the current flood window + unsigned long change_start; // Time when relay was scheduled to change + unsigned long change_delay; // Delay until the next change + bool report; // Whether to report to own topic + bool group_report; // Whether to report to group topic // Helping objects - Ticker pulseTicker; // Holds the pulse back timer + Ticker pulseTicker; // Holds the pulse back timer }; @@ -320,7 +335,7 @@ void _relayProcess(bool mode) { if (target != mode) continue; // Only process if the change delay has expired - if (millis() - _relays[id].change_start < _relays[id].change_delay) continue; + if (_relays[id].change_delay && (millis() - _relays[id].change_start < _relays[id].change_delay)) continue; // Purge existing delay in case of cancelation _relays[id].change_delay = 0; @@ -351,8 +366,8 @@ void _relayProcess(bool mode) { // We will trigger a eeprom save only if // we care about current relay status on boot - unsigned char boot_mode = getSetting("relayBoot", id, RELAY_BOOT_MODE).toInt(); - bool save_eeprom = ((RELAY_BOOT_SAME == boot_mode) || (RELAY_BOOT_TOGGLE == boot_mode)); + const auto boot_mode = getSetting("relayBoot", id, RELAY_BOOT_MODE).toInt(); + const bool save_eeprom = ((RELAY_BOOT_SAME == boot_mode) || (RELAY_BOOT_TOGGLE == boot_mode)); _relay_save_timer.once_ms(RELAY_SAVE_DELAY, relaySave, save_eeprom); } @@ -720,7 +735,6 @@ void _relayBackwards() { void _relayBoot() { _relayRecursive = true; - bool trigger_save = false; const auto stored_mask = rtcmemStatus() ? _relayMaskRtcmem() @@ -745,9 +759,8 @@ void _relayBoot() { status = mask.test(i); break; case RELAY_BOOT_TOGGLE: - status = !mask[i]; mask.flip(i); - trigger_save = true; + status = mask[i]; break; case RELAY_BOOT_LOCKED_ON: status = true; @@ -766,7 +779,11 @@ void _relayBoot() { _relays[i].current_status = !status; _relays[i].target_status = status; + _relays[i].change_start = millis(); + _relays[i].change_delay = status + ? _relays[i].delay_on + : _relays[i].delay_off; #if RELAY_PROVIDER == RELAY_PROVIDER_STM // XXX hack for correctly restoring relay state on boot @@ -776,13 +793,6 @@ void _relayBoot() { _relays[i].lock = lock; - } - - _relayMaskRtcmem(mask); - - // Save if there is any relay in the RELAY_BOOT_TOGGLE mode - if (trigger_save) { - _relayMaskSettings(mask); } _relayRecursive = false; From 19e474abca9149a42da3df65f67f670b072c1928 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Sat, 23 Nov 2019 00:48:20 +0300 Subject: [PATCH 6/7] utils: fix base-10 condition --- code/espurna/utils.ino | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index b2461713..fb8017a1 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -704,13 +704,10 @@ uint32_t u32fromString(const String& string) { base = 8; } else if (string.startsWith("0x")) { base = 16; - } else { - return 0; } - return u32fromString(string.substring(2), base); } - return u32fromString(string, base); + return u32fromString((base == 10) ? string : string.substring(2), base); } String u32toString(uint32_t value, int base) { From 57c9359622b3a1e0729dc78ec54d7d70d02676ce Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Sat, 23 Nov 2019 00:49:10 +0300 Subject: [PATCH 7/7] utils: interval value is in seconds --- code/espurna/utils.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index fb8017a1..b5eedd54 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -247,7 +247,7 @@ void heartbeat() { #if MQTT_SUPPORT if (!serial && (_heartbeat_mode == HEARTBEAT_REPEAT || systemGetHeartbeat())) { if (hb_cfg & Heartbeat::Interval) - mqttSend(MQTT_TOPIC_INTERVAL, String(getHeartbeatInterval() / 1000).c_str()); + mqttSend(MQTT_TOPIC_INTERVAL, String(getHeartbeatInterval()).c_str()); if (hb_cfg & Heartbeat::App) mqttSend(MQTT_TOPIC_APP, APP_NAME);