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