diff --git a/code/espurna/alexa.ino b/code/espurna/alexa.cpp similarity index 97% rename from code/espurna/alexa.ino rename to code/espurna/alexa.cpp index 072bd994..4d53ca09 100644 --- a/code/espurna/alexa.ino +++ b/code/espurna/alexa.cpp @@ -6,18 +6,27 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "alexa.h" + #if ALEXA_SUPPORT #include -#include "alexa.h" #include "broker.h" +#include "light.h" #include "relay.h" -#include "ws.h" #include "web.h" +#include "ws.h" + +struct alexa_queue_element_t { + unsigned char device_id; + bool state; + unsigned char value; +}; + +static std::queue _alexa_queue; fauxmoESP _alexa; -static std::queue _alexa_queue; // ----------------------------------------------------------------------------- // ALEXA @@ -76,6 +85,32 @@ bool alexaEnabled() { return getSetting("alexaEnabled", 1 == ALEXA_ENABLED); } +void alexaLoop() { + + _alexa.handle(); + + while (!_alexa_queue.empty()) { + + alexa_queue_element_t element = _alexa_queue.front(); + DEBUG_MSG_P(PSTR("[ALEXA] Device #%u state: %s value: %d\n"), element.device_id, element.state ? "ON" : "OFF", element.value); + + #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT + if (0 == element.device_id) { + relayStatus(0, element.state); + } else { + lightState(element.device_id - 1, element.state); + lightChannel(element.device_id - 1, element.value); + lightUpdate(true, true); + } + #else + relayStatus(element.device_id, element.state); + #endif + + _alexa_queue.pop(); + } + +} + void alexaSetup() { // Backwards compatibility @@ -154,30 +189,4 @@ void alexaSetup() { } -void alexaLoop() { - - _alexa.handle(); - - while (!_alexa_queue.empty()) { - - alexa_queue_element_t element = _alexa_queue.front(); - DEBUG_MSG_P(PSTR("[ALEXA] Device #%u state: %s value: %d\n"), element.device_id, element.state ? "ON" : "OFF", element.value); - - #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT - if (0 == element.device_id) { - relayStatus(0, element.state); - } else { - lightState(element.device_id - 1, element.state); - lightChannel(element.device_id - 1, element.value); - lightUpdate(true, true); - } - #else - relayStatus(element.device_id, element.state); - #endif - - _alexa_queue.pop(); - } - -} - #endif diff --git a/code/espurna/alexa.h b/code/espurna/alexa.h index 9b9346b3..5f89d9b5 100644 --- a/code/espurna/alexa.h +++ b/code/espurna/alexa.h @@ -8,13 +8,7 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once -#include "web.h" - -struct alexa_queue_element_t { - unsigned char device_id; - bool state; - unsigned char value; -}; +#include "espurna.h" #if ALEXA_SUPPORT diff --git a/code/espurna/api.ino b/code/espurna/api.cpp similarity index 99% rename from code/espurna/api.ino rename to code/espurna/api.cpp index e60dd701..3ee8b4c9 100644 --- a/code/espurna/api.ino +++ b/code/espurna/api.cpp @@ -6,19 +6,22 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "api.h" + #if API_SUPPORT -#include "api.h" +#include + #include "system.h" #include "web.h" #include "rpc.h" #include "ws.h" -typedef struct { +struct web_api_t { char * key; api_get_callback_f getFn = NULL; api_put_callback_f putFn = NULL; -} web_api_t; +}; std::vector _apis; // ----------------------------------------------------------------------------- diff --git a/code/espurna/api.h b/code/espurna/api.h index 54e86ee8..9abde434 100644 --- a/code/espurna/api.h +++ b/code/espurna/api.h @@ -8,24 +8,21 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" #include "web.h" #include -// TODO: need these prototypes for .ino +#if WEB_SUPPORT && API_SUPPORT + +#include +#include + using api_get_callback_f = std::function; using api_put_callback_f = std::function ; -#if API_SUPPORT +void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = nullptr); -#include -#include -#include - -#include - -#if WEB_SUPPORT - void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = nullptr); -#endif +void apiSetup(); #endif // API_SUPPORT == 1 diff --git a/code/espurna/board.ino b/code/espurna/board.cpp similarity index 99% rename from code/espurna/board.ino rename to code/espurna/board.cpp index 0c6b1805..fdb3af1d 100644 --- a/code/espurna/board.ino +++ b/code/espurna/board.cpp @@ -5,6 +5,8 @@ BOARD MODULE */ #include "board.h" +#include "relay.h" +#include "sensor.h" //-------------------------------------------------------------------------------- diff --git a/code/espurna/board.h b/code/espurna/board.h index a8274e57..e842b4fd 100644 --- a/code/espurna/board.h +++ b/code/espurna/board.h @@ -6,6 +6,8 @@ BOARD MODULE #pragma once +#include "espurna.h" + const String& getChipId(); const String& getIdentifier(); diff --git a/code/espurna/broker.h b/code/espurna/broker.h index 7e6fee52..8f221d1c 100644 --- a/code/espurna/broker.h +++ b/code/espurna/broker.h @@ -8,7 +8,7 @@ Copyright (C) 2017-2019 by Xose Pérez #pragma once -#if BROKER_SUPPORT +#include "espurna.h" #include #include @@ -46,14 +46,9 @@ struct TBroker { template TBrokerCallbacks TBroker::callbacks; - -// --- Some known types. Bind them here to avoid .ino screwing with order --- - using StatusBroker = TBroker; using SensorReadBroker = TBroker; using SensorReportBroker = TBroker; using ConfigBroker = TBroker; - -#endif // BROKER_SUPPORT == 1 diff --git a/code/espurna/button.ino b/code/espurna/button.cpp similarity index 99% rename from code/espurna/button.ino rename to code/espurna/button.cpp index 7f5b7796..a1351f01 100644 --- a/code/espurna/button.ino +++ b/code/espurna/button.cpp @@ -6,6 +6,8 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "button.h" + #if BUTTON_SUPPORT #include @@ -15,10 +17,11 @@ Copyright (C) 2016-2019 by Xose Pérez #include "compat.h" #include "gpio.h" #include "system.h" +#include "mqtt.h" #include "relay.h" #include "light.h" +#include "ws.h" -#include "button.h" #include "button_config.h" #include "libs/DebounceEvent.h" @@ -125,6 +128,10 @@ debounce_event::types::Config _buttonConfig(unsigned char index) { }; } +int _buttonEventNumber(button_event_t event) { + return static_cast(event); +} + // ----------------------------------------------------------------------------- button_event_delays_t::button_event_delays_t() : @@ -335,10 +342,6 @@ button_action_t buttonAction(unsigned char id, const button_event_t event) { return _buttonDecodeEventAction(_buttons[id].actions, event); } -int _buttonEventNumber(button_event_t event) { - return static_cast(event); -} - // Approach based on https://github.com/esp8266/Arduino/pull/6950 // "PROGMEM footprint cleanup for responseCodeToString (#6950)" // In this particular case, saves 76 bytes (120 vs 44) @@ -493,6 +496,90 @@ unsigned long _buttonGetSetting(const char* key, unsigned char index, T default_ return getSetting({key, index}, getSetting(key, default_value)); } +// Sonoff Dual does not do real GPIO readings and we +// depend on the external MCU to send us relay / button events +// Lightfox uses the same protocol as Dual, but has slightly different actions +// TODO: move this to a separate 'hardware' setup file? + +void _buttonLoopSonoffDual() { + + if (Serial.available() < 4) { + return; + } + + unsigned char bytes[4] = {0}; + Serial.readBytes(bytes, 4); + if ((bytes[0] != 0xA0) && (bytes[1] != 0x04) && (bytes[3] != 0xA1)) { + return; + } + + const unsigned char value [[gnu::unused]] = bytes[2]; + +#if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL + + // RELAYs and BUTTONs are synchonized in the SIL F330 + // The on-board BUTTON2 should toggle RELAY0 value + // Since we are not passing back RELAY2 value + // (in the relayStatus method) it will only be present + // here if it has actually been pressed + if ((value & 4) == 4) { + buttonEvent(2, button_event_t::Click); + return; + } + + // Otherwise check if any of the other two BUTTONs + // (in the header) has been pressed, but we should + // ensure that we only toggle one of them to avoid + // the synchronization going mad + // This loop is generic for any PSB-04 module + for (unsigned int i=0; i 0; + + // Check if the status for that relay has changed + if (relayStatus(i) != status) { + buttonEvent(i, button_event_t::Click); + break; + } + + } + +#elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL + + DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value); + + for (unsigned int i=0; i<_buttons.size(); i++) { + if ((value & (1 << i)) > 0) { + buttonEvent(i, button_event_t::Click); + } + } + +#endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL + +} + +void _buttonLoopGeneric() { + for (size_t id = 0; id < _buttons.size(); ++id) { + auto event = _buttons[id].loop(); + if (event != button_event_t::None) { + buttonEvent(id, event); + } + } +} + +void buttonLoop() { + + #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC + _buttonLoopGeneric(); + #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \ + (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL) + _buttonLoopSonoffDual(); + #else + #warning "Unknown value for BUTTON_EVENTS_SOURCE" + #endif + +} + void buttonSetup() { // Backwards compatibility @@ -624,88 +711,4 @@ void buttonSetup() { } -// Sonoff Dual does not do real GPIO readings and we -// depend on the external MCU to send us relay / button events -// Lightfox uses the same protocol as Dual, but has slightly different actions -// TODO: move this to a separate 'hardware' setup file? - -void _buttonLoopSonoffDual() { - - if (Serial.available() < 4) { - return; - } - - unsigned char bytes[4] = {0}; - Serial.readBytes(bytes, 4); - if ((bytes[0] != 0xA0) && (bytes[1] != 0x04) && (bytes[3] != 0xA1)) { - return; - } - - const unsigned char value [[gnu::unused]] = bytes[2]; - -#if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL - - // RELAYs and BUTTONs are synchonized in the SIL F330 - // The on-board BUTTON2 should toggle RELAY0 value - // Since we are not passing back RELAY2 value - // (in the relayStatus method) it will only be present - // here if it has actually been pressed - if ((value & 4) == 4) { - buttonEvent(2, button_event_t::Click); - return; - } - - // Otherwise check if any of the other two BUTTONs - // (in the header) has been pressed, but we should - // ensure that we only toggle one of them to avoid - // the synchronization going mad - // This loop is generic for any PSB-04 module - for (unsigned int i=0; i 0; - - // Check if the status for that relay has changed - if (relayStatus(i) != status) { - buttonEvent(i, button_event_t::Click); - break; - } - - } - -#elif BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL - - DEBUG_MSG_P(PSTR("[BUTTON] [LIGHTFOX] Received buttons mask: %u\n"), value); - - for (unsigned int i=0; i<_buttons.size(); i++) { - if ((value & (1 << i)) > 0) { - buttonEvent(i, button_event_t::Click); - } - } - -#endif // BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL - -} - -void _buttonLoopGeneric() { - for (size_t id = 0; id < _buttons.size(); ++id) { - auto event = _buttons[id].loop(); - if (event != button_event_t::None) { - buttonEvent(id, event); - } - } -} - -void buttonLoop() { - - #if BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_GENERIC - _buttonLoopGeneric(); - #elif (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_ITEAD_SONOFF_DUAL) || \ - (BUTTON_EVENTS_SOURCE == BUTTON_EVENTS_SOURCE_FOXEL_LIGHTFOX_DUAL) - _buttonLoopSonoffDual(); - #else - #warning "Unknown value for BUTTON_EVENTS_SOURCE" - #endif - -} - #endif // BUTTON_SUPPORT diff --git a/code/espurna/button.h b/code/espurna/button.h index 73a896d6..eaf648a8 100644 --- a/code/espurna/button.h +++ b/code/espurna/button.h @@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #include "libs/BasePin.h" #include "libs/DebounceEvent.h" diff --git a/code/espurna/button_config.h b/code/espurna/button_config.h index 1b154ac9..2660c635 100644 --- a/code/espurna/button_config.h +++ b/code/espurna/button_config.h @@ -6,6 +6,8 @@ BUTTON MODULE #pragma once +#include "espurna.h" + namespace ButtonMask { enum { diff --git a/code/espurna/compat.h b/code/espurna/compat.h index 9ccde247..4374cae1 100644 --- a/code/espurna/compat.h +++ b/code/espurna/compat.h @@ -6,6 +6,8 @@ COMPATIBILITY BETWEEN 2.3.0 and latest versions #pragma once +#include "espurna.h" + // ----------------------------------------------------------------------------- // Core version 2.4.2 and higher changed the cont_t structure to a pointer: // https://github.com/esp8266/Arduino/commit/5d5ea92a4d004ab009d5f642629946a0cb8893dd#diff-3fa12668b289ccb95b7ab334833a4ba8L35 @@ -71,16 +73,42 @@ extern "C" { long __attribute__((deprecated("Please avoid using map() with Core 2.3.0"))) map(long x, long in_min, long in_max, long out_min, long out_max); #endif +// ----------------------------------------------------------------------------- +// Proxy min & max same as the latest Arduino.h +// ----------------------------------------------------------------------------- + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) + +#undef min +#undef max +#undef _min +#undef _max + +#include + +using std::min; +using std::max; +using std::isinf; +using std::isnan; + +#define _min(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a < _b? _a : _b; }) +#define _max(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a > _b? _a : _b; }) + +#endif + // ----------------------------------------------------------------------------- // std::make_unique backport for C++11, since we still use it // ----------------------------------------------------------------------------- #if 201103L >= __cplusplus + +#include namespace std { template std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } } + #endif #define UNUSED(x) (void)(x) diff --git a/code/espurna/crash.ino b/code/espurna/crash.cpp similarity index 99% rename from code/espurna/crash.ino rename to code/espurna/crash.cpp index d2054a3c..7da4b075 100644 --- a/code/espurna/crash.ino +++ b/code/espurna/crash.cpp @@ -4,6 +4,8 @@ // https://github.com/krzychb/EspSaveCrash // ----------------------------------------------------------------------------- +#include "crash.h" + #if DEBUG_SUPPORT #include @@ -11,7 +13,6 @@ #include "system.h" #include "storage_eeprom.h" -#include "crash.h" uint16_t _save_crash_stack_trace_max = SAVE_CRASH_STACK_TRACE_MAX; bool _save_crash_enabled = true; diff --git a/code/espurna/crash.h b/code/espurna/crash.h index 707127b2..27adddd2 100644 --- a/code/espurna/crash.h +++ b/code/espurna/crash.h @@ -6,6 +6,11 @@ #pragma once +#include "espurna.h" + +#include +#include + #define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data /** @@ -42,7 +47,6 @@ constexpr size_t crashUsedSpace() { return (SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_SIZE + 2); } - void crashClear(); void crashDump(); void crashSetup(); diff --git a/code/espurna/debug.ino b/code/espurna/debug.cpp similarity index 98% rename from code/espurna/debug.ino rename to code/espurna/debug.cpp index f6a38dc6..6d11d584 100644 --- a/code/espurna/debug.ino +++ b/code/espurna/debug.cpp @@ -6,14 +6,17 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "debug.h" + #if DEBUG_SUPPORT #include #include #include -#include "debug.h" +#include "settings.h" #include "telnet.h" +#include "web.h" #include "ws.h" #if DEBUG_UDP_SUPPORT @@ -335,10 +338,10 @@ void debugConfigure() { { #if defined(DEBUG_ESP_PORT) #if not defined(NDEBUG) - constexpr const bool debug_sdk = true; + constexpr bool debug_sdk = true; #endif // !defined(NDEBUG) #else - constexpr const bool debug_sdk = false; + constexpr bool debug_sdk = false; #endif // defined(DEBUG_ESP_PORT) DEBUG_PORT.setDebugOutput(getSetting("dbgSDK", debug_sdk)); diff --git a/code/espurna/debug.h b/code/espurna/debug.h index f4de095f..9bf6600b 100644 --- a/code/espurna/debug.h +++ b/code/espurna/debug.h @@ -6,7 +6,11 @@ DEBUG MODULE #pragma once -#include +#include "espurna.h" + +#if DEBUG_WEB_SUPPORT +#include +#endif extern "C" { void custom_crash_callback(struct rst_info*, uint32_t, uint32_t); @@ -25,7 +29,7 @@ bool debugLogBuffer(); void debugWebSetup(); void debugConfigure(); -void debugConfigueBoot(); +void debugConfigureBoot(); void debugSetup(); void debugSend(const char* format, ...); diff --git a/code/espurna/domoticz.ino b/code/espurna/domoticz.cpp similarity index 99% rename from code/espurna/domoticz.ino rename to code/espurna/domoticz.cpp index 03fd1a81..68dff106 100644 --- a/code/espurna/domoticz.ino +++ b/code/espurna/domoticz.cpp @@ -6,13 +6,16 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "domoticz.h" + #if DOMOTICZ_SUPPORT #include "broker.h" -#include "domoticz.h" -#include "sensor.h" +#include "light.h" #include "mqtt.h" #include "relay.h" +#include "sensor.h" +#include "ws.h" bool _dcz_enabled = false; std::bitset _dcz_relay_state; @@ -171,6 +174,23 @@ void _domoticzMqtt(unsigned int type, const char * topic, char * payload) { }; +void _domoticzRelayConfigure(size_t size) { + for (size_t n = 0; n < size; ++n) { + _dcz_relay_state[n] = relayStatus(n); + } +} + +void _domoticzConfigure() { + const bool enabled = getSetting("dczEnabled", 1 == DOMOTICZ_ENABLED); + if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled); + + #if RELAY_SUPPORT + _domoticzRelayConfigure(relayCount()); + #endif + + _dcz_enabled = enabled; +} + #if BROKER_SUPPORT void _domoticzConfigCallback(const String& key, const String& value) { @@ -259,30 +279,13 @@ void _domoticzWebSocketOnConnected(JsonObject& root) { } #if SENSOR_SUPPORT - _sensorWebSocketMagnitudes(root, "dcz"); + sensorWebSocketMagnitudes(root, "dcz"); #endif } #endif // WEB_SUPPORT -void _domoticzRelayConfigure(size_t size) { - for (size_t n = 0; n < size; ++n) { - _dcz_relay_state[n] = relayStatus(n); - } -} - -void _domoticzConfigure() { - const bool enabled = getSetting("dczEnabled", 1 == DOMOTICZ_ENABLED); - if (enabled != _dcz_enabled) _domoticzMqttSubscribe(enabled); - - #if RELAY_SUPPORT - _domoticzRelayConfigure(relayCount()); - #endif - - _dcz_enabled = enabled; -} - //------------------------------------------------------------------------------ // Public API //------------------------------------------------------------------------------ diff --git a/code/espurna/domoticz.h b/code/espurna/domoticz.h index d563ba2b..e26d8fac 100644 --- a/code/espurna/domoticz.h +++ b/code/espurna/domoticz.h @@ -8,10 +8,11 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #if DOMOTICZ_SUPPORT #include - #include template diff --git a/code/espurna/encoder.ino b/code/espurna/encoder.cpp similarity index 100% rename from code/espurna/encoder.ino rename to code/espurna/encoder.cpp diff --git a/code/espurna/espurna.h b/code/espurna/espurna.h new file mode 100644 index 00000000..7000711a --- /dev/null +++ b/code/espurna/espurna.h @@ -0,0 +1,49 @@ +/* + +ESPurna + +Copyright (C) 2016-2019 by Xose Pérez + +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 . + +*/ + +#pragma once + +#include "config/all.h" + +#include "board.h" +#include "debug.h" +#include "compat.h" +#include "wifi.h" +#include "storage_eeprom.h" +#include "gpio.h" +#include "settings.h" +#include "system.h" +#include "terminal.h" +#include "utils.h" + +#include +#include +#include +#include +#include + +using void_callback_f = void (*)(); + +void espurnaRegisterLoop(void_callback_f callback); +void espurnaRegisterReload(void_callback_f callback); +void espurnaReload(); +unsigned long espurnaLoopDelay(); + diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 6a867a02..40d5182b 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -19,305 +19,48 @@ along with this program. If not, see . */ -#include "config/all.h" +// !!! NOTICE !!! +// +// This file is only for compatibility with Arduino IDE / arduino-cli +// See main.cpp +// -#include -#include -#include -#include -#include - -#include "board.h" -#include "compat.h" -#include "storage_eeprom.h" -#include "gpio.h" -#include "settings.h" -#include "system.h" -#include "terminal.h" -#include "utils.h" -#include "wifi.h" +#include "espurna.h" #include "alexa.h" #include "api.h" #include "broker.h" #include "button.h" +#include "crash.h" #include "debug.h" #include "domoticz.h" #include "homeassistant.h" #include "i2c.h" +#include "influxdb.h" #include "ir.h" #include "led.h" +#include "light.h" +#include "llmnr.h" +#include "mdns.h" #include "mqtt.h" +#include "netbios.h" +#include "nofuss.h" #include "ntp.h" #include "ota.h" #include "relay.h" +#include "rfbridge.h" #include "rfm69.h" #include "rpc.h" #include "rpnrules.h" #include "rtcmem.h" +#include "scheduler.h" #include "sensor.h" +#include "ssdp.h" +#include "telnet.h" #include "thermostat.h" +#include "thingspeak.h" #include "tuya.h" +#include "uartmqtt.h" #include "web.h" #include "ws.h" -#include "libs/URL.h" -#include "libs/HeapStats.h" - -using void_callback_f = void (*)(); - -std::vector _loop_callbacks; -std::vector _reload_callbacks; - -bool _reload_config = false; -unsigned long _loop_delay = 0; - -// ----------------------------------------------------------------------------- -// GENERAL CALLBACKS -// ----------------------------------------------------------------------------- - -void espurnaRegisterLoop(void_callback_f callback) { - _loop_callbacks.push_back(callback); -} - -void espurnaRegisterReload(void_callback_f callback) { - _reload_callbacks.push_back(callback); -} - -void espurnaReload() { - _reload_config = true; -} - -void _espurnaReload() { - for (const auto& callback : _reload_callbacks) { - callback(); - } -} - -unsigned long espurnaLoopDelay() { - return _loop_delay; -} - -// ----------------------------------------------------------------------------- -// BOOTING -// ----------------------------------------------------------------------------- - -void setup() { - - // ------------------------------------------------------------------------- - // Basic modules, will always run - // ------------------------------------------------------------------------- - - // Cache initial free heap value - setInitialFreeHeap(); - - // Init logging module - #if DEBUG_SUPPORT - debugSetup(); - #endif - - // Init GPIO functions - gpioSetup(); - - // Init RTCMEM - rtcmemSetup(); - - // Init EEPROM - eepromSetup(); - - // Init persistance - settingsSetup(); - - // Configure logger and crash recorder - #if DEBUG_SUPPORT - debugConfigureBoot(); - crashSetup(); - #endif - - // Return bogus free heap value for broken devices - // XXX: device is likely to trigger other bugs! tread carefuly - wtfHeap(getSetting("wtfHeap", 0)); - - // Init Serial, SPIFFS and system check - systemSetup(); - - // Init terminal features - #if TERMINAL_SUPPORT - terminalSetup(); - #endif - - // Hostname & board name initialization - if (getSetting("hostname").length() == 0) { - setDefaultHostname(); - } - setBoardName(); - - // Show welcome message and system configuration - info(true); - - wifiSetup(); - #if OTA_ARDUINOOTA_SUPPORT - arduinoOtaSetup(); - #endif - #if TELNET_SUPPORT - telnetSetup(); - #endif - #if OTA_CLIENT != OTA_CLIENT_NONE - otaClientSetup(); - #endif - - // ------------------------------------------------------------------------- - // Check if system is stable - // ------------------------------------------------------------------------- - - #if SYSTEM_CHECK_ENABLED - if (!systemCheck()) return; - #endif - - // ------------------------------------------------------------------------- - // Next modules will be only loaded if system is flagged as stable - // ------------------------------------------------------------------------- - - // Init webserver required before any module that uses API - #if WEB_SUPPORT - webSetup(); - wsSetup(); - #if DEBUG_WEB_SUPPORT - debugWebSetup(); - #endif - #if OTA_WEB_SUPPORT - otaWebSetup(); - #endif - #endif - #if API_SUPPORT - apiSetup(); - #endif - - // lightSetup must be called before relaySetup - #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE - lightSetup(); - #endif - #if RELAY_SUPPORT - relaySetup(); - #endif - #if BUTTON_SUPPORT - buttonSetup(); - #endif - #if ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) - encoderSetup(); - #endif - #if LED_SUPPORT - ledSetup(); - #endif - - #if MQTT_SUPPORT - mqttSetup(); - #endif - #if MDNS_SERVER_SUPPORT - mdnsServerSetup(); - #endif - #if MDNS_CLIENT_SUPPORT - mdnsClientSetup(); - #endif - #if LLMNR_SUPPORT - llmnrSetup(); - #endif - #if NETBIOS_SUPPORT - netbiosSetup(); - #endif - #if SSDP_SUPPORT - ssdpSetup(); - #endif - #if NTP_SUPPORT - ntpSetup(); - #endif - #if I2C_SUPPORT - i2cSetup(); - #endif - #if RF_SUPPORT - rfbSetup(); - #endif - #if ALEXA_SUPPORT - alexaSetup(); - #endif - #if NOFUSS_SUPPORT - nofussSetup(); - #endif - #if SENSOR_SUPPORT - sensorSetup(); - #endif - #if INFLUXDB_SUPPORT - idbSetup(); - #endif - #if THINGSPEAK_SUPPORT - tspkSetup(); - #endif - #if RFM69_SUPPORT - rfm69Setup(); - #endif - #if IR_SUPPORT - irSetup(); - #endif - #if DOMOTICZ_SUPPORT - domoticzSetup(); - #endif - #if HOMEASSISTANT_SUPPORT - haSetup(); - #endif - #if SCHEDULER_SUPPORT - schSetup(); - #endif - #if RPN_RULES_SUPPORT - rpnSetup(); - #endif - #if UART_MQTT_SUPPORT - uartmqttSetup(); - #endif - #ifdef FOXEL_LIGHTFOX_DUAL - lightfoxSetup(); - #endif - #if THERMOSTAT_SUPPORT - thermostatSetup(); - #endif - #if THERMOSTAT_DISPLAY_SUPPORT - displaySetup(); - #endif - #if TUYA_SUPPORT - tuyaSetup(); - #endif - - // 3rd party code hook - #if USE_EXTRA - extraSetup(); - #endif - - // Prepare configuration for version 2.0 - migrate(); - - // Set up delay() after loop callbacks are finished - // Note: should be after settingsSetup() - _loop_delay = constrain( - getSetting("loopDelay", LOOP_DELAY_TIME), 0, 300 - ); - - saveSettings(); - -} - -void loop() { - - // Reload config before running any callbacks - if (_reload_config) { - _espurnaReload(); - _reload_config = false; - } - - // Call registered loop callbacks - for (unsigned char i = 0; i < _loop_callbacks.size(); i++) { - (_loop_callbacks[i])(); - } - - // Power saving delay - if (_loop_delay) delay(_loop_delay); - -} diff --git a/code/espurna/gpio.ino b/code/espurna/gpio.cpp similarity index 100% rename from code/espurna/gpio.ino rename to code/espurna/gpio.cpp diff --git a/code/espurna/gpio.h b/code/espurna/gpio.h index b6181dd8..4fe04b0c 100644 --- a/code/espurna/gpio.h +++ b/code/espurna/gpio.h @@ -8,6 +8,9 @@ Copyright (C) 2017-2019 by Xose Pérez #pragma once +#include + +#include "espurna.h" #include "libs/BasePin.h" constexpr const size_t GpioPins = 17; diff --git a/code/espurna/homeassistant.ino b/code/espurna/homeassistant.cpp similarity index 99% rename from code/espurna/homeassistant.ino rename to code/espurna/homeassistant.cpp index 551d0b9a..e3033d59 100644 --- a/code/espurna/homeassistant.ino +++ b/code/espurna/homeassistant.cpp @@ -6,15 +6,18 @@ Copyright (C) 2017-2019 by Xose Pérez */ +#include "homeassistant.h" + #if HOMEASSISTANT_SUPPORT #include #include -#include "homeassistant.h" +#include "light.h" #include "mqtt.h" #include "relay.h" #include "rpc.h" +#include "sensor.h" #include "utils.h" #include "ws.h" diff --git a/code/espurna/homeassistant.h b/code/espurna/homeassistant.h index 7afc7da8..a790fb05 100644 --- a/code/espurna/homeassistant.h +++ b/code/espurna/homeassistant.h @@ -8,6 +8,8 @@ Copyright (C) 2017-2019 by Xose Pérez #pragma once +#include "espurna.h" + #if HOMEASSISTANT_SUPPORT #include diff --git a/code/espurna/i2c.ino b/code/espurna/i2c.cpp similarity index 99% rename from code/espurna/i2c.ino rename to code/espurna/i2c.cpp index ba019e12..914fe421 100644 --- a/code/espurna/i2c.ino +++ b/code/espurna/i2c.cpp @@ -6,6 +6,8 @@ Copyright (C) 2017-2019 by Xose Pérez */ +#include "i2c.h" + #if I2C_SUPPORT unsigned int _i2c_locked[16] = {0}; diff --git a/code/espurna/i2c.h b/code/espurna/i2c.h index da5b9352..d5b574ff 100644 --- a/code/espurna/i2c.h +++ b/code/espurna/i2c.h @@ -8,6 +8,8 @@ Copyright (C) 2017-2019 by Xose Pérez #pragma once +#include "espurna.h" + #if I2C_SUPPORT #if I2C_USE_BRZO @@ -38,5 +40,9 @@ bool i2cGetLock(unsigned char address); bool i2cReleaseLock(unsigned char address); unsigned char i2cFindAndLock(size_t size, unsigned char * addresses); +unsigned char i2cFind(size_t size, unsigned char * addresses, unsigned char &start); +unsigned char i2cFind(size_t size, unsigned char * addresses); + +void i2cSetup(); #endif // I2C_SUPPORT == 1 diff --git a/code/espurna/influxdb.ino b/code/espurna/influxdb.cpp similarity index 99% rename from code/espurna/influxdb.ino rename to code/espurna/influxdb.cpp index 25f67277..4b7b16d8 100644 --- a/code/espurna/influxdb.ino +++ b/code/espurna/influxdb.cpp @@ -6,12 +6,16 @@ Copyright (C) 2017-2019 by Xose Pérez */ +#include "influxdb.h" + #if INFLUXDB_SUPPORT #include #include #include "broker.h" +#include "ws.h" +#include "terminal.h" #include "libs/AsyncClientHelpers.h" const char InfluxDb_http_success[] = "HTTP/1.1 204"; diff --git a/code/espurna/influxdb.h b/code/espurna/influxdb.h new file mode 100644 index 00000000..6e8e0ade --- /dev/null +++ b/code/espurna/influxdb.h @@ -0,0 +1,21 @@ +/* + +INFLUXDB MODULE + +Copyright (C) 2017-2019 by Xose Pérez + +*/ + +#include "espurna.h" + +#if INFLUXDB_SUPPORT + +#include + +bool idbSend(const char * topic, unsigned char id, const char * payload); +bool idbSend(const char * topic, const char * payload); +bool idbEnabled(); +void idbSetup(); + +#endif // INFLUXDB_SUPPORT + diff --git a/code/espurna/ir.ino b/code/espurna/ir.cpp similarity index 99% rename from code/espurna/ir.ino rename to code/espurna/ir.cpp index a0f4a61e..50433228 100644 --- a/code/espurna/ir.ino +++ b/code/espurna/ir.cpp @@ -46,9 +46,11 @@ Raw messages: -------------------------------------------------------------------------------- */ +#include "ir.h" + #if IR_SUPPORT -#include "ir.h" +#include "light.h" #include "mqtt.h" #include "relay.h" diff --git a/code/espurna/ir.h b/code/espurna/ir.h index 3bc20449..6eaa757c 100644 --- a/code/espurna/ir.h +++ b/code/espurna/ir.h @@ -10,6 +10,8 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #if IR_SUPPORT #include "ir_button.h" diff --git a/code/espurna/ir_button.h b/code/espurna/ir_button.h index 887fbd0e..285bf563 100644 --- a/code/espurna/ir_button.h +++ b/code/espurna/ir_button.h @@ -10,6 +10,8 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + // Remote Buttons SET 1 (for the original Remote shipped with the controller) #if IR_BUTTON_SET == 1 diff --git a/code/espurna/led.ino b/code/espurna/led.cpp similarity index 99% rename from code/espurna/led.ino rename to code/espurna/led.cpp index 9d936332..3bc26abf 100644 --- a/code/espurna/led.ino +++ b/code/espurna/led.cpp @@ -6,15 +6,18 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "led.h" + #if LED_SUPPORT #include #include "broker.h" +#include "mqtt.h" #include "relay.h" #include "rpc.h" +#include "ws.h" -#include "led.h" #include "led_pattern.h" #include "led_config.h" @@ -305,76 +308,6 @@ void ledUpdate(bool do_update) { _led_update = do_update; } -void ledSetup() { - - size_t leds = 0; - - #if LED1_PIN != GPIO_NONE - ++leds; - #endif - #if LED2_PIN != GPIO_NONE - ++leds; - #endif - #if LED3_PIN != GPIO_NONE - ++leds; - #endif - #if LED4_PIN != GPIO_NONE - ++leds; - #endif - #if LED5_PIN != GPIO_NONE - ++leds; - #endif - #if LED6_PIN != GPIO_NONE - ++leds; - #endif - #if LED7_PIN != GPIO_NONE - ++leds; - #endif - #if LED8_PIN != GPIO_NONE - ++leds; - #endif - - _leds.reserve(leds); - - for (unsigned char index=0; index < LedsMax; ++index) { - const auto pin = getSetting({"ledGPIO", index}, _ledPin(index)); - if (!gpioValid(pin)) { - break; - } - _leds.emplace_back( - pin, - getSetting({"ledInv", index}, _ledInverse(index)), - getSetting({"ledMode", index}, _ledMode(index)), - getSetting({"ledRelay", index}, _ledRelay(index)) - ); - } - - _led_update = true; - - #if MQTT_SUPPORT - mqttRegister(_ledMQTTCallback); - #endif - - #if WEB_SUPPORT - wsRegister() - .onVisible(_ledWebSocketOnVisible) - .onConnected(_ledWebSocketOnConnected) - .onKeyCheck(_ledWebSocketOnKeyCheck); - #endif - - #if BROKER_SUPPORT - StatusBroker::Register(_ledBrokerCallback); - #endif - - - DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size()); - - // Main callbacks - espurnaRegisterLoop(ledLoop); - espurnaRegisterReload(_ledConfigure); - -} - void ledLoop() { const auto wifi_state = wifiState(); @@ -498,4 +431,75 @@ void ledLoop() { } +void ledSetup() { + + size_t leds = 0; + + #if LED1_PIN != GPIO_NONE + ++leds; + #endif + #if LED2_PIN != GPIO_NONE + ++leds; + #endif + #if LED3_PIN != GPIO_NONE + ++leds; + #endif + #if LED4_PIN != GPIO_NONE + ++leds; + #endif + #if LED5_PIN != GPIO_NONE + ++leds; + #endif + #if LED6_PIN != GPIO_NONE + ++leds; + #endif + #if LED7_PIN != GPIO_NONE + ++leds; + #endif + #if LED8_PIN != GPIO_NONE + ++leds; + #endif + + _leds.reserve(leds); + + for (unsigned char index=0; index < LedsMax; ++index) { + const auto pin = getSetting({"ledGPIO", index}, _ledPin(index)); + if (!gpioValid(pin)) { + break; + } + _leds.emplace_back( + pin, + getSetting({"ledInv", index}, _ledInverse(index)), + getSetting({"ledMode", index}, _ledMode(index)), + getSetting({"ledRelay", index}, _ledRelay(index)) + ); + } + + _led_update = true; + + #if MQTT_SUPPORT + mqttRegister(_ledMQTTCallback); + #endif + + #if WEB_SUPPORT + wsRegister() + .onVisible(_ledWebSocketOnVisible) + .onConnected(_ledWebSocketOnConnected) + .onKeyCheck(_ledWebSocketOnKeyCheck); + #endif + + #if BROKER_SUPPORT + StatusBroker::Register(_ledBrokerCallback); + #endif + + + DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size()); + + // Main callbacks + espurnaRegisterLoop(ledLoop); + espurnaRegisterReload(_ledConfigure); + +} + + #endif // LED_SUPPORT diff --git a/code/espurna/led.h b/code/espurna/led.h index 0dd4f03f..5f765d5d 100644 --- a/code/espurna/led.h +++ b/code/espurna/led.h @@ -8,10 +8,12 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #include #include -constexpr const size_t LedsMax = 8; +constexpr size_t LedsMax = 8; enum class LedMode { NetworkAutoconfig, diff --git a/code/espurna/led_config.h b/code/espurna/led_config.h index 443aa5d0..b8df5caf 100644 --- a/code/espurna/led_config.h +++ b/code/espurna/led_config.h @@ -6,6 +6,8 @@ LED MODULE #pragma once +#include "espurna.h" + constexpr const unsigned char _ledPin(unsigned char index) { return ( (index == 0) ? LED1_PIN : diff --git a/code/espurna/led_pattern.h b/code/espurna/led_pattern.h index 56b1045c..8ec5b66e 100644 --- a/code/espurna/led_pattern.h +++ b/code/espurna/led_pattern.h @@ -10,10 +10,10 @@ Copyright (C) 2020 by Maxim Prokhorov #pragma once -#include - #include "led.h" +#include + // Scans input string with format // ',, ,, ...' // Directly changing `led.pattern.delays` contents diff --git a/code/espurna/led_pattern.h.in b/code/espurna/led_pattern.h.in index 0c740bb4..7d5bf8f2 100644 --- a/code/espurna/led_pattern.h.in +++ b/code/espurna/led_pattern.h.in @@ -8,10 +8,10 @@ Copyright (C) 2020 by Maxim Prokhorov #pragma once -#include - #include "led.h" +#include + // Scans input string with format // ',, ,, ...' // Directly changing `led.pattern.delays` contents diff --git a/code/espurna/libs/BasePin.h b/code/espurna/libs/BasePin.h index 5c90d661..6e6c5a8d 100644 --- a/code/espurna/libs/BasePin.h +++ b/code/espurna/libs/BasePin.h @@ -8,6 +8,8 @@ Copyright (C) 2020 by Maxim Prokhorov #pragma once +#include + // base interface for generic pin handler. class BasePin { public: diff --git a/code/espurna/libs/HeapStats.h b/code/espurna/libs/HeapStats.h deleted file mode 100644 index a74752b4..00000000 --- a/code/espurna/libs/HeapStats.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - -Show extended heap stats when EspClass::getHeapStats() is available - -*/ - -#pragma once - -#include "TypeChecks.h" - -struct heap_stats_t { - uint32_t available; - uint16_t usable; - uint8_t frag_pct; -}; - -namespace heap_stats { - template - using has_getHeapStats_t = decltype(std::declval().getHeapStats(0,0,0)); - - template - using has_getHeapStats = is_detected; -} - -template -void _getHeapStats(const std::true_type&, T& instance, heap_stats_t& stats) { - instance.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct); -} - -template -void _getHeapStats(const std::false_type&, T& instance, heap_stats_t& stats) { - stats.available = instance.getFreeHeap(); - stats.usable = 0; - stats.frag_pct = 0; -} - -void getHeapStats(heap_stats_t& stats) { - _getHeapStats(heap_stats::has_getHeapStats{}, ESP, stats); -} - -// WTF -// Calling ESP.getFreeHeap() is making the system crash on a specific -// AiLight bulb, but anywhere else it should work as expected -static bool _heap_value_wtf = false; - -heap_stats_t getHeapStats() { - heap_stats_t stats; - if (_heap_value_wtf) { - stats.available = 9999; - stats.usable = 9999; - stats.frag_pct = 0; - return stats; - } - getHeapStats(stats); - return stats; -} - -void wtfHeap(bool value) { - _heap_value_wtf = value; -} - -unsigned int getFreeHeap() { - return ESP.getFreeHeap(); -} - -static unsigned int _initial_heap_value = 0; -void setInitialFreeHeap() { - _initial_heap_value = getFreeHeap(); -} - -unsigned int getInitialFreeHeap() { - if (0 == _initial_heap_value) { - setInitialFreeHeap(); - } - return _initial_heap_value; -} - -void infoMemory(const char* name, const heap_stats_t& stats) { - infoMemory(name, getInitialFreeHeap(), stats.available); -} - -void infoHeapStats(const char* name, const heap_stats_t& stats) { - DEBUG_MSG_P( - PSTR("[MAIN] %-6s: %5u contiguous bytes available (%u%% fragmentation)\n"), - name, - stats.usable, - stats.frag_pct - ); -} - -void infoHeapStats(bool show_frag_stats = true) { - const auto stats = getHeapStats(); - infoMemory("Heap", stats); - if (show_frag_stats && heap_stats::has_getHeapStats{}) { - infoHeapStats("Heap", stats); - } -} diff --git a/code/espurna/libs/NtpClientWrap.h b/code/espurna/libs/NtpClientWrap.h deleted file mode 100644 index ea8ea295..00000000 --- a/code/espurna/libs/NtpClientWrap.h +++ /dev/null @@ -1,33 +0,0 @@ -// ----------------------------------------------------------------------------- -// NtpClient overrides to avoid triggering network sync -// ----------------------------------------------------------------------------- - -#pragma once - -#if NTP_LEGACY_SUPPORT - -#include -#include - -class NTPClientWrap : public NTPClient { - -public: - - NTPClientWrap() : NTPClient() { - udp = new WiFiUDP(); - _lastSyncd = 0; - } - - bool setInterval(int shortInterval, int longInterval) { - _shortInterval = shortInterval; - _longInterval = longInterval; - return true; - } - -}; - -// NOTE: original NTP should be discarded by the linker -// TODO: allow NTP client object to be destroyed -NTPClientWrap NTPw; - -#endif diff --git a/code/espurna/libs/RFM69Wrap.h b/code/espurna/libs/RFM69Wrap.h index 33322bef..d5b2ec29 100644 --- a/code/espurna/libs/RFM69Wrap.h +++ b/code/espurna/libs/RFM69Wrap.h @@ -26,40 +26,3 @@ along with this program. If not, see . #include #include -class RFM69Wrap: public RFM69_ATC { - - public: - - RFM69Wrap(uint8_t slaveSelectPin=RF69_SPI_CS, uint8_t interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, uint8_t interruptNum=0): - RFM69_ATC(slaveSelectPin, interruptPin, isRFM69HW, interruptNum) {}; - - protected: - - // overriding SPI_CLOCK for ESP8266 - void select() { - - noInterrupts(); - - #if defined (SPCR) && defined (SPSR) - // save current SPI settings - _SPCR = SPCR; - _SPSR = SPSR; - #endif - - // set RFM69 SPI settings - SPI.setDataMode(SPI_MODE0); - SPI.setBitOrder(MSBFIRST); - - #if defined(__arm__) - SPI.setClockDivider(SPI_CLOCK_DIV16); - #elif defined(ARDUINO_ARCH_ESP8266) - SPI.setClockDivider(SPI_CLOCK_DIV2); // speeding it up for the ESP8266 - #else - SPI.setClockDivider(SPI_CLOCK_DIV4); - #endif - - digitalWrite(_slaveSelectPin, LOW); - - } - -}; diff --git a/code/espurna/libs/SecureClientHelpers.h b/code/espurna/libs/SecureClientHelpers.h index 42f9ee4e..d656a87b 100644 --- a/code/espurna/libs/SecureClientHelpers.h +++ b/code/espurna/libs/SecureClientHelpers.h @@ -4,8 +4,12 @@ #pragma once +#include "../espurna.h" + #if SECURE_CLIENT != SECURE_CLIENT_NONE +#include "../ntp.h" + #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL #include #elif SECURE_CLIENT == SECURE_CLIENT_AXTLS @@ -20,7 +24,8 @@ using fp_callback_f = std::function; using cert_callback_f = std::function; using mfln_callback_f = std::function; -const char * _secureClientCheckAsString(int check) { +// TODO: workaround for `multiple definition of `SecureClientHelpers::_secureClientCheckAsString(int);' +inline const char * _secureClientCheckAsString(int check) { switch (check) { case SECURE_CLIENT_CHECK_NONE: return "no validation"; case SECURE_CLIENT_CHECK_FINGERPRINT: return "fingerprint validation"; diff --git a/code/espurna/libs/URL.h b/code/espurna/libs/URL.h index f8b786c6..ec5d5007 100644 --- a/code/espurna/libs/URL.h +++ b/code/espurna/libs/URL.h @@ -10,70 +10,66 @@ class URL { public: - URL(); - URL(const String&); - String protocol; - String host; - String path; - uint16_t port; + URL() : + protocol(), + host(), + path(), + port(0) + {} + + URL(const String& string) { + _parse(string); + } + + String protocol; + String host; + String path; + uint16_t port; private: - void _parse(String); + + void _parse(String buffer) { + // cut the protocol part + int index = buffer.indexOf("://"); + if (index > 0) { + this->protocol = buffer.substring(0, index); + buffer.remove(0, (index + 3)); + } + + if (this->protocol == "http") { + this->port = 80; + } else if (this->protocol == "https") { + this->port = 443; + } + + // cut the host part + String _host; + + index = buffer.indexOf('/'); + if (index >= 0) { + _host = buffer.substring(0, index); + } else { + _host = buffer; + } + + // store the remaining part as path + if (index >= 0) { + buffer.remove(0, index); + this->path = buffer; + } else { + this->path = "/"; + } + + // separate host from port, when present + index = _host.indexOf(':'); + if (index >= 0) { + this->port = _host.substring(index + 1).toInt(); + this->host = _host.substring(0, index); + } else { + this->host = _host; + } + } + }; -URL::URL() : - protocol(), - host(), - path(), - port(0) -{} - -URL::URL(const String& string) { - _parse(string); -} - -void URL::_parse(String buffer) { - - // cut the protocol part - int index = buffer.indexOf("://"); - if (index > 0) { - this->protocol = buffer.substring(0, index); - buffer.remove(0, (index + 3)); - } - - if (this->protocol == "http") { - this->port = 80; - } else if (this->protocol == "https") { - this->port = 443; - } - - // cut the host part - String _host; - - index = buffer.indexOf('/'); - if (index >= 0) { - _host = buffer.substring(0, index); - } else { - _host = buffer; - } - - // store the remaining part as path - if (index >= 0) { - buffer.remove(0, index); - this->path = buffer; - } else { - this->path = "/"; - } - - // separate host from port, when present - index = _host.indexOf(':'); - if (index >= 0) { - this->port = _host.substring(index + 1).toInt(); - this->host = _host.substring(0, index); - } else { - this->host = _host; - } - -} - diff --git a/code/espurna/light.ino b/code/espurna/light.cpp similarity index 98% rename from code/espurna/light.ino rename to code/espurna/light.cpp index 4fbd9fd1..393c3106 100644 --- a/code/espurna/light.ino +++ b/code/espurna/light.cpp @@ -6,14 +6,17 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "light.h" + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE +#include "api.h" #include "broker.h" #include "mqtt.h" +#include "rtcmem.h" #include "tuya.h" #include "ws.h" -#include "light.h" #include "light_config.h" #include @@ -25,7 +28,6 @@ extern "C" { #include "libs/fs_math.h" } -#define ARRAYINIT(type, name, ...) type name[] = {__VA_ARGS__}; #if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER // default is 8, we only need up to 5 @@ -38,6 +40,21 @@ extern "C" { // ----------------------------------------------------------------------------- +struct channel_t { + + channel_t(); + channel_t(unsigned char pin, bool inverse); + + unsigned char pin; // real GPIO pin + bool inverse; // whether we should invert the value before using it + bool state; // is the channel ON + unsigned char inputValue; // raw value, without the brightness + unsigned char value; // normalized value, including brightness + unsigned char target; // target value + double current; // transition value + +}; + Ticker _light_comms_ticker; Ticker _light_save_ticker; Ticker _light_transition_ticker; @@ -74,7 +91,9 @@ light_brightness_func_t* _light_brightness_func = nullptr; #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX #include my92xx * _my92xx; -ARRAYINIT(unsigned char, _light_channel_map, MY92XX_MAPPING); +unsigned char _light_channel_map[] { + MY92XX_MAPPING +}; #endif // UI hint about channel distribution @@ -566,6 +585,8 @@ void _lightTransition(unsigned long step) { } +void _lightProviderScheduleUpdate(unsigned long steps); + void _lightProviderUpdate(unsigned long steps) { if (_light_provider_update) return; @@ -829,6 +850,255 @@ void lightBroker() { // API // ----------------------------------------------------------------------------- +#if API_SUPPORT + +void _lightAPISetup() { + + if (_light_has_color) { + + apiRegister(MQTT_TOPIC_COLOR_RGB, + [](char * buffer, size_t len) { + if (getSetting("useCSS", 1 == LIGHT_USE_CSS)) { + _toRGB(buffer, len, true); + } else { + _toLong(buffer, len, true); + } + }, + [](const char * payload) { + lightColor(payload, true); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_COLOR_HSV, + [](char * buffer, size_t len) { + _toHSV(buffer, len); + }, + [](const char * payload) { + lightColor(payload, false); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_KELVIN, + [](char * buffer, size_t len) {}, + [](const char * payload) { + _lightAdjustKelvin(payload); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_MIRED, + [](char * buffer, size_t len) {}, + [](const char * payload) { + _lightAdjustMireds(payload); + lightUpdate(true, true); + } + ); + + } + + for (unsigned int id=0; id<_light_channels.size(); id++) { + + char key[15]; + snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); + apiRegister(key, + [id](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), _light_channels[id].target); + }, + [id](const char * payload) { + _lightAdjustChannel(id, payload); + lightUpdate(true, true); + } + ); + + } + + apiRegister(MQTT_TOPIC_TRANSITION, + [](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), lightTransitionTime()); + }, + [](const char * payload) { + lightTransitionTime(atol(payload)); + } + ); + + apiRegister(MQTT_TOPIC_BRIGHTNESS, + [](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), _light_brightness); + }, + [](const char * payload) { + _lightAdjustBrightness(payload); + lightUpdate(true, true); + } + ); + +} + +#endif // API_SUPPORT + + +#if WEB_SUPPORT + +bool _lightWebSocketOnKeyCheck(const char * key, JsonVariant& value) { + if (strncmp(key, "light", 5) == 0) return true; + if (strncmp(key, "use", 3) == 0) return true; + return false; +} + +void _lightWebSocketStatus(JsonObject& root) { + if (_light_has_color) { + if (getSetting("useRGB", 1 == LIGHT_USE_RGB)) { + root["rgb"] = lightColor(true); + } else { + root["hsv"] = lightColor(false); + } + } + if (_light_use_cct) { + JsonObject& mireds = root.createNestedObject("mireds"); + mireds["value"] = _light_mireds; + mireds["cold"] = _light_cold_mireds; + mireds["warm"] = _light_warm_mireds; + root["useCCT"] = _light_use_cct; + } + JsonArray& channels = root.createNestedArray("channels"); + for (unsigned char id=0; id < _light_channels.size(); id++) { + channels.add(lightChannel(id)); + } + root["brightness"] = lightBrightness(); +} + +void _lightWebSocketOnVisible(JsonObject& root) { + root["colorVisible"] = 1; +} + +void _lightWebSocketOnConnected(JsonObject& root) { + root["mqttGroupColor"] = getSetting("mqttGroupColor"); + root["useColor"] = _light_has_color; + root["useWhite"] = _light_use_white; + root["useGamma"] = _light_use_gamma; + root["useTransitions"] = _light_use_transitions; + root["useCSS"] = getSetting("useCSS", 1 == LIGHT_USE_CSS); + root["useRGB"] = getSetting("useRGB", 1 == LIGHT_USE_RGB); + root["lightTime"] = _light_transition_time; + + _lightWebSocketStatus(root); +} + +void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { + + if (_light_has_color) { + if (strcmp(action, "color") == 0) { + if (data.containsKey("rgb")) { + lightColor(data["rgb"], true); + lightUpdate(true, true); + } + if (data.containsKey("hsv")) { + lightColor(data["hsv"], false); + lightUpdate(true, true); + } + } + } + + if (_light_use_cct) { + if (strcmp(action, "mireds") == 0) { + _fromMireds(data["mireds"]); + lightUpdate(true, true); + } + } + + + if (strcmp(action, "channel") == 0) { + if (data.containsKey("id") && data.containsKey("value")) { + lightChannel(data["id"].as(), data["value"].as()); + lightUpdate(true, true); + } + } + + if (strcmp(action, "brightness") == 0) { + if (data.containsKey("value")) { + lightBrightness(data["value"].as()); + lightUpdate(true, true); + } + } + +} + +#endif + +#if TERMINAL_SUPPORT + +void _lightChannelDebug(unsigned char id) { + DEBUG_MSG_P(PSTR("Channel #%u (%s): %d\n"), id, lightDesc(id).c_str(), lightChannel(id)); +} + +void _lightInitCommands() { + + terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) { + if (e->argc > 1) { + _lightAdjustBrightness(e->argv[1]); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Brightness: %u\n"), lightBrightness()); + terminalOK(); + }); + + terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) { + if (!lightChannels()) return; + + auto id = -1; + if (e->argc > 1) { + id = String(e->argv[1]).toInt(); + } + + if (id < 0 || id >= static_cast(lightChannels())) { + for (unsigned char index = 0; index < lightChannels(); ++index) { + _lightChannelDebug(index); + } + return; + } + + if (e->argc > 2) { + _lightAdjustChannel(id, e->argv[2]); + lightUpdate(true, true); + } + + _lightChannelDebug(id); + + terminalOK(); + }); + + terminalRegisterCommand(F("COLOR"), [](Embedis* e) { + if (e->argc > 1) { + lightColor(e->argv[1]); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); + terminalOK(); + }); + + terminalRegisterCommand(F("KELVIN"), [](Embedis* e) { + if (e->argc > 1) { + _lightAdjustKelvin(e->argv[1]); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); + terminalOK(); + }); + + terminalRegisterCommand(F("MIRED"), [](Embedis* e) { + if (e->argc > 1) { + _lightAdjustMireds(e->argv[1]); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); + terminalOK(); + }); + +} + +#endif // TERMINAL_SUPPORT + size_t lightChannels() { return _light_channels.size(); } @@ -841,7 +1111,7 @@ bool lightUseCCT() { return _light_use_cct; } -void _lightComms(const unsigned char mask) { +void _lightComms(unsigned char mask) { // Report color and brightness to MQTT broker #if MQTT_SUPPORT @@ -995,12 +1265,12 @@ unsigned int lightTransitionTime() { } } -void lightTransitionTime(unsigned long m) { - if (0 == m) { +void lightTransitionTime(unsigned long ms) { + if (0 == ms) { _light_use_transitions = false; } else { _light_use_transitions = true; - _light_transition_time = m; + _light_transition_time = ms; } setSetting("useTransitions", _light_use_transitions); setSetting("lightTime", _light_transition_time); @@ -1011,254 +1281,6 @@ void lightTransitionTime(unsigned long m) { // SETUP // ----------------------------------------------------------------------------- -#if WEB_SUPPORT - -bool _lightWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - if (strncmp(key, "light", 5) == 0) return true; - if (strncmp(key, "use", 3) == 0) return true; - return false; -} - -void _lightWebSocketStatus(JsonObject& root) { - if (_light_has_color) { - if (getSetting("useRGB", 1 == LIGHT_USE_RGB)) { - root["rgb"] = lightColor(true); - } else { - root["hsv"] = lightColor(false); - } - } - if (_light_use_cct) { - JsonObject& mireds = root.createNestedObject("mireds"); - mireds["value"] = _light_mireds; - mireds["cold"] = _light_cold_mireds; - mireds["warm"] = _light_warm_mireds; - root["useCCT"] = _light_use_cct; - } - JsonArray& channels = root.createNestedArray("channels"); - for (unsigned char id=0; id < _light_channels.size(); id++) { - channels.add(lightChannel(id)); - } - root["brightness"] = lightBrightness(); -} - -void _lightWebSocketOnVisible(JsonObject& root) { - root["colorVisible"] = 1; -} - -void _lightWebSocketOnConnected(JsonObject& root) { - root["mqttGroupColor"] = getSetting("mqttGroupColor"); - root["useColor"] = _light_has_color; - root["useWhite"] = _light_use_white; - root["useGamma"] = _light_use_gamma; - root["useTransitions"] = _light_use_transitions; - root["useCSS"] = getSetting("useCSS", 1 == LIGHT_USE_CSS); - root["useRGB"] = getSetting("useRGB", 1 == LIGHT_USE_RGB); - root["lightTime"] = _light_transition_time; - - _lightWebSocketStatus(root); -} - -void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { - - if (_light_has_color) { - if (strcmp(action, "color") == 0) { - if (data.containsKey("rgb")) { - lightColor(data["rgb"], true); - lightUpdate(true, true); - } - if (data.containsKey("hsv")) { - lightColor(data["hsv"], false); - lightUpdate(true, true); - } - } - } - - if (_light_use_cct) { - if (strcmp(action, "mireds") == 0) { - _fromMireds(data["mireds"]); - lightUpdate(true, true); - } - } - - - if (strcmp(action, "channel") == 0) { - if (data.containsKey("id") && data.containsKey("value")) { - lightChannel(data["id"].as(), data["value"].as()); - lightUpdate(true, true); - } - } - - if (strcmp(action, "brightness") == 0) { - if (data.containsKey("value")) { - lightBrightness(data["value"].as()); - lightUpdate(true, true); - } - } - -} - -#endif - -#if API_SUPPORT - -void _lightAPISetup() { - - if (_light_has_color) { - - apiRegister(MQTT_TOPIC_COLOR_RGB, - [](char * buffer, size_t len) { - if (getSetting("useCSS", 1 == LIGHT_USE_CSS)) { - _toRGB(buffer, len, true); - } else { - _toLong(buffer, len, true); - } - }, - [](const char * payload) { - lightColor(payload, true); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_COLOR_HSV, - [](char * buffer, size_t len) { - _toHSV(buffer, len); - }, - [](const char * payload) { - lightColor(payload, false); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_KELVIN, - [](char * buffer, size_t len) {}, - [](const char * payload) { - _lightAdjustKelvin(payload); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_MIRED, - [](char * buffer, size_t len) {}, - [](const char * payload) { - _lightAdjustMireds(payload); - lightUpdate(true, true); - } - ); - - } - - for (unsigned int id=0; id<_light_channels.size(); id++) { - - char key[15]; - snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); - apiRegister(key, - [id](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), _light_channels[id].target); - }, - [id](const char * payload) { - _lightAdjustChannel(id, payload); - lightUpdate(true, true); - } - ); - - } - - apiRegister(MQTT_TOPIC_TRANSITION, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), lightTransitionTime()); - }, - [](const char * payload) { - lightTransitionTime(atol(payload)); - } - ); - - apiRegister(MQTT_TOPIC_BRIGHTNESS, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), _light_brightness); - }, - [](const char * payload) { - _lightAdjustBrightness(payload); - lightUpdate(true, true); - } - ); - -} - -#endif // API_SUPPORT - -#if TERMINAL_SUPPORT - -void _lightChannelDebug(unsigned char id) { - DEBUG_MSG_P(PSTR("Channel #%u (%s): %d\n"), id, lightDesc(id).c_str(), lightChannel(id)); -} - -void _lightInitCommands() { - - terminalRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) { - if (e->argc > 1) { - _lightAdjustBrightness(e->argv[1]); - lightUpdate(true, true); - } - DEBUG_MSG_P(PSTR("Brightness: %u\n"), lightBrightness()); - terminalOK(); - }); - - terminalRegisterCommand(F("CHANNEL"), [](Embedis* e) { - if (!lightChannels()) return; - - auto id = -1; - if (e->argc > 1) { - id = String(e->argv[1]).toInt(); - } - - if (id < 0 || id >= static_cast(lightChannels())) { - for (unsigned char index = 0; index < lightChannels(); ++index) { - _lightChannelDebug(index); - } - return; - } - - if (e->argc > 2) { - _lightAdjustChannel(id, e->argv[2]); - lightUpdate(true, true); - } - - _lightChannelDebug(id); - - terminalOK(); - }); - - terminalRegisterCommand(F("COLOR"), [](Embedis* e) { - if (e->argc > 1) { - lightColor(e->argv[1]); - lightUpdate(true, true); - } - DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); - terminalOK(); - }); - - terminalRegisterCommand(F("KELVIN"), [](Embedis* e) { - if (e->argc > 1) { - _lightAdjustKelvin(e->argv[1]); - lightUpdate(true, true); - } - DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); - terminalOK(); - }); - - terminalRegisterCommand(F("MIRED"), [](Embedis* e) { - if (e->argc > 1) { - _lightAdjustMireds(e->argv[1]); - lightUpdate(true, true); - } - DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); - terminalOK(); - }); - -} - -#endif // TERMINAL_SUPPORT - #if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER const unsigned long _light_iomux[16] PROGMEM = { PERIPHS_IO_MUX_GPIO0_U, PERIPHS_IO_MUX_U0TXD_U, PERIPHS_IO_MUX_GPIO2_U, PERIPHS_IO_MUX_U0RXD_U, @@ -1375,7 +1397,7 @@ void lightSetup() { #endif #if LIGHT_PROVIDER == LIGHT_PROVIDER_TUYA - tuyaSetupLight(); + Tuya::tuyaSetupLight(); #endif DEBUG_MSG_P(PSTR("[LIGHT] LIGHT_PROVIDER = %d\n"), LIGHT_PROVIDER); diff --git a/code/espurna/light.h b/code/espurna/light.h index d4b2d73a..41f925cc 100644 --- a/code/espurna/light.h +++ b/code/espurna/light.h @@ -4,18 +4,21 @@ #pragma once +#include "espurna.h" + +// TODO: lowercase namespace Light { - constexpr const size_t ChannelsMax = 5; + constexpr size_t ChannelsMax = 5; - constexpr const long VALUE_MIN = LIGHT_MIN_VALUE; - constexpr const long VALUE_MAX = LIGHT_MAX_VALUE; + constexpr long VALUE_MIN = LIGHT_MIN_VALUE; + constexpr long VALUE_MAX = LIGHT_MAX_VALUE; - constexpr const long BRIGHTNESS_MIN = LIGHT_MIN_BRIGHTNESS; - constexpr const long BRIGHTNESS_MAX = LIGHT_MAX_BRIGHTNESS; + constexpr long BRIGHTNESS_MIN = LIGHT_MIN_BRIGHTNESS; + constexpr long BRIGHTNESS_MAX = LIGHT_MAX_BRIGHTNESS; - constexpr const long PWM_MIN = LIGHT_MIN_PWM; - constexpr const long PWM_MAX = LIGHT_MAX_PWM; - constexpr const long PWM_LIMIT = LIGHT_LIMIT_PWM; + constexpr long PWM_MIN = LIGHT_MIN_PWM; + constexpr long PWM_MAX = LIGHT_MAX_PWM; + constexpr long PWM_LIMIT = LIGHT_LIMIT_PWM; enum Communications : unsigned char { COMMS_NONE = 0, @@ -24,22 +27,15 @@ namespace Light { }; } -struct channel_t { - - channel_t(); - channel_t(unsigned char pin, bool inverse); - - unsigned char pin; // real GPIO pin - bool inverse; // whether we should invert the value before using it - bool state; // is the channel ON - unsigned char inputValue; // raw value, without the brightness - unsigned char value; // normalized value, including brightness - unsigned char target; // target value - double current; // transition value - -}; - size_t lightChannels(); +unsigned int lightTransitionTime(); +void lightTransitionTime(unsigned long ms); + +void lightColor(const char * color, bool rgb); +void lightColor(const char * color); +void lightColor(unsigned long color); +String lightColor(bool rgb); +String lightColor(); void lightState(unsigned char i, bool state); bool lightState(unsigned char i); @@ -55,3 +51,14 @@ void lightChannel(unsigned char id, long value); void lightBrightnessStep(long steps, long multiplier = LIGHT_STEP); void lightChannelStep(unsigned char id, long steps, long multiplier = LIGHT_STEP); + +void lightUpdate(bool save, bool forward, bool group_forward); +void lightUpdate(bool save, bool forward); + +bool lightHasColor(); +bool lightUseCCT(); + +void lightMQTT(); +void lightSetupChannels(unsigned char size); + +void lightSetup(); diff --git a/code/espurna/light_config.h b/code/espurna/light_config.h index 49a4a46c..570711f7 100644 --- a/code/espurna/light_config.h +++ b/code/espurna/light_config.h @@ -6,6 +6,8 @@ LIGHT MODULE #pragma once +#include "espurna.h" + constexpr const unsigned char _lightEnablePin() { return LIGHT_ENABLE_PIN; } diff --git a/code/espurna/lightfox.ino b/code/espurna/lightfox.cpp similarity index 100% rename from code/espurna/lightfox.ino rename to code/espurna/lightfox.cpp diff --git a/code/espurna/llmnr.ino b/code/espurna/llmnr.cpp similarity index 90% rename from code/espurna/llmnr.ino rename to code/espurna/llmnr.cpp index 9b96b1a9..6b8b472a 100644 --- a/code/espurna/llmnr.ino +++ b/code/espurna/llmnr.cpp @@ -6,9 +6,9 @@ Copyright (C) 2017-2019 by Xose Pérez */ -#if LLMNR_SUPPORT +#include "llmnr.h" -#include +#if LLMNR_SUPPORT void llmnrSetup() { LLMNR.begin(getSetting("hostname").c_str()); diff --git a/code/espurna/llmnr.h b/code/espurna/llmnr.h new file mode 100644 index 00000000..28a1ec28 --- /dev/null +++ b/code/espurna/llmnr.h @@ -0,0 +1,16 @@ +/* + +LLMNR MODULE + +Copyright (C) 2017-2019 by Xose Pérez + +*/ + +#include "espurna.h" + +#if LLMNR_SUPPORT + +#include +void llmnrSetup(); + +#endif // LLMNR_SUPPORT diff --git a/code/espurna/main.cpp b/code/espurna/main.cpp new file mode 100644 index 00000000..c5c4489f --- /dev/null +++ b/code/espurna/main.cpp @@ -0,0 +1,315 @@ +/* + +ESPurna + +Copyright (C) 2016-2019 by Xose Pérez + +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 . + +*/ + +#include "espurna.h" + +#include "alexa.h" +#include "api.h" +#include "broker.h" +#include "button.h" +#include "crash.h" +#include "debug.h" +#include "domoticz.h" +#include "homeassistant.h" +#include "i2c.h" +#include "influxdb.h" +#include "ir.h" +#include "led.h" +#include "light.h" +#include "llmnr.h" +#include "mdns.h" +#include "mqtt.h" +#include "netbios.h" +#include "nofuss.h" +#include "ntp.h" +#include "ota.h" +#include "relay.h" +#include "rfbridge.h" +#include "rfm69.h" +#include "rpc.h" +#include "rpnrules.h" +#include "rtcmem.h" +#include "scheduler.h" +#include "sensor.h" +#include "ssdp.h" +#include "telnet.h" +#include "thermostat.h" +#include "thingspeak.h" +#include "tuya.h" +#include "uartmqtt.h" +#include "web.h" +#include "ws.h" + +std::vector _loop_callbacks; +std::vector _reload_callbacks; + +bool _reload_config = false; +unsigned long _loop_delay = 0; + +// ----------------------------------------------------------------------------- +// GENERAL CALLBACKS +// ----------------------------------------------------------------------------- + +void espurnaRegisterLoop(void_callback_f callback) { + _loop_callbacks.push_back(callback); +} + +void espurnaRegisterReload(void_callback_f callback) { + _reload_callbacks.push_back(callback); +} + +void espurnaReload() { + _reload_config = true; +} + +void _espurnaReload() { + for (const auto& callback : _reload_callbacks) { + callback(); + } +} + +unsigned long espurnaLoopDelay() { + return _loop_delay; +} + +// ----------------------------------------------------------------------------- +// BOOTING +// ----------------------------------------------------------------------------- + +void setup() { + + // ------------------------------------------------------------------------- + // Basic modules, will always run + // ------------------------------------------------------------------------- + + // Cache initial free heap value + setInitialFreeHeap(); + + // Init logging module + #if DEBUG_SUPPORT + debugSetup(); + #endif + + // Init GPIO functions + gpioSetup(); + + // Init RTCMEM + rtcmemSetup(); + + // Init EEPROM + eepromSetup(); + + // Init persistance + settingsSetup(); + + // Configure logger and crash recorder + #if DEBUG_SUPPORT + debugConfigureBoot(); + crashSetup(); + #endif + + // Return bogus free heap value for broken devices + // XXX: device is likely to trigger other bugs! tread carefuly + wtfHeap(getSetting("wtfHeap", 0)); + + // Init Serial, SPIFFS and system check + systemSetup(); + + // Init terminal features + #if TERMINAL_SUPPORT + terminalSetup(); + #endif + + // Hostname & board name initialization + if (getSetting("hostname").length() == 0) { + setDefaultHostname(); + } + setBoardName(); + + // Show welcome message and system configuration + info(true); + + wifiSetup(); + #if OTA_ARDUINOOTA_SUPPORT + arduinoOtaSetup(); + #endif + #if TELNET_SUPPORT + telnetSetup(); + #endif + #if OTA_CLIENT != OTA_CLIENT_NONE + otaClientSetup(); + #endif + + // ------------------------------------------------------------------------- + // Check if system is stable + // ------------------------------------------------------------------------- + + #if SYSTEM_CHECK_ENABLED + if (!systemCheck()) return; + #endif + + // ------------------------------------------------------------------------- + // Next modules will be only loaded if system is flagged as stable + // ------------------------------------------------------------------------- + + // Init webserver required before any module that uses API + #if WEB_SUPPORT + webSetup(); + wsSetup(); + #if DEBUG_WEB_SUPPORT + debugWebSetup(); + #endif + #if OTA_WEB_SUPPORT + otaWebSetup(); + #endif + #endif + #if API_SUPPORT + apiSetup(); + #endif + + // lightSetup must be called before relaySetup + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + lightSetup(); + #endif + #if RELAY_SUPPORT + relaySetup(); + #endif + #if BUTTON_SUPPORT + buttonSetup(); + #endif + #if ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) + encoderSetup(); + #endif + #if LED_SUPPORT + ledSetup(); + #endif + + #if MQTT_SUPPORT + mqttSetup(); + #endif + #if MDNS_SERVER_SUPPORT + mdnsServerSetup(); + #endif + #if MDNS_CLIENT_SUPPORT + mdnsClientSetup(); + #endif + #if LLMNR_SUPPORT + llmnrSetup(); + #endif + #if NETBIOS_SUPPORT + netbiosSetup(); + #endif + #if SSDP_SUPPORT + ssdpSetup(); + #endif + #if NTP_SUPPORT + ntpSetup(); + #endif + #if I2C_SUPPORT + i2cSetup(); + #endif + #if RF_SUPPORT + rfbSetup(); + #endif + #if ALEXA_SUPPORT + alexaSetup(); + #endif + #if NOFUSS_SUPPORT + nofussSetup(); + #endif + #if SENSOR_SUPPORT + sensorSetup(); + #endif + #if INFLUXDB_SUPPORT + idbSetup(); + #endif + #if THINGSPEAK_SUPPORT + tspkSetup(); + #endif + #if RFM69_SUPPORT + rfm69Setup(); + #endif + #if IR_SUPPORT + irSetup(); + #endif + #if DOMOTICZ_SUPPORT + domoticzSetup(); + #endif + #if HOMEASSISTANT_SUPPORT + haSetup(); + #endif + #if SCHEDULER_SUPPORT + schSetup(); + #endif + #if RPN_RULES_SUPPORT + rpnSetup(); + #endif + #if UART_MQTT_SUPPORT + uartmqttSetup(); + #endif + #ifdef FOXEL_LIGHTFOX_DUAL + lightfoxSetup(); + #endif + #if THERMOSTAT_SUPPORT + thermostatSetup(); + #endif + #if THERMOSTAT_DISPLAY_SUPPORT + displaySetup(); + #endif + #if TUYA_SUPPORT + Tuya::tuyaSetup(); + #endif + + // 3rd party code hook + #if USE_EXTRA + extraSetup(); + #endif + + // Prepare configuration for version 2.0 + migrate(); + + // Set up delay() after loop callbacks are finished + // Note: should be after settingsSetup() + _loop_delay = constrain( + getSetting("loopDelay", LOOP_DELAY_TIME), 0, 300 + ); + + saveSettings(); + +} + +void loop() { + + // Reload config before running any callbacks + if (_reload_config) { + _espurnaReload(); + _reload_config = false; + } + + // Call registered loop callbacks + for (unsigned char i = 0; i < _loop_callbacks.size(); i++) { + (_loop_callbacks[i])(); + } + + // Power saving delay + if (_loop_delay) delay(_loop_delay); + +} diff --git a/code/espurna/mdns.ino b/code/espurna/mdns.cpp similarity index 98% rename from code/espurna/mdns.ino rename to code/espurna/mdns.cpp index d6060381..8c858741 100644 --- a/code/espurna/mdns.ino +++ b/code/espurna/mdns.cpp @@ -10,6 +10,11 @@ Copyright (C) 2017-2019 by Xose Pérez // mDNS Server // ----------------------------------------------------------------------------- +#include "mdns.h" + +#include "mqtt.h" +#include "utils.h" + #if MDNS_SERVER_SUPPORT #include diff --git a/code/espurna/mdns.h b/code/espurna/mdns.h new file mode 100644 index 00000000..834afaa8 --- /dev/null +++ b/code/espurna/mdns.h @@ -0,0 +1,11 @@ +#pragma once + +#include "espurna.h" +#include + +#if MDNS_SERVER_SUPPORT + +#include +void mdnsServerSetup(); + +#endif diff --git a/code/espurna/migrate.ino b/code/espurna/migrate.cpp similarity index 100% rename from code/espurna/migrate.ino rename to code/espurna/migrate.cpp diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.cpp similarity index 99% rename from code/espurna/mqtt.ino rename to code/espurna/mqtt.cpp index 8d1d82b6..79ca7999 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.cpp @@ -7,18 +7,20 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot */ -#if MQTT_SUPPORT - #include "mqtt.h" +#if MQTT_SUPPORT + #include #include #include #include "system.h" +#include "mdns.h" #include "mqtt.h" #include "ntp.h" #include "rpc.h" +#include "rtcmem.h" #include "ws.h" #include "libs/AsyncClientHelpers.h" @@ -215,58 +217,6 @@ bool _mqttConnectSyncClient(bool secure = false) { #endif // (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT) -void _mqttConnect() { - - // Do not connect if disabled - if (!_mqtt_enabled) return; - - // Do not connect if already connected or still trying to connect - if (_mqtt.connected() || (_mqtt_state != AsyncClientState::Disconnected)) return; - - // Check reconnect interval - if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return; - - // Increase the reconnect delay - _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP; - if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) { - _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX; - } - - #if MDNS_CLIENT_SUPPORT - _mqtt_server = mdnsResolve(_mqtt_server); - #endif - - DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%u\n"), _mqtt_server.c_str(), _mqtt_port); - - DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid.c_str()); - DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos); - DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0); - DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive); - DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will.c_str()); - - _mqtt_state = AsyncClientState::Connecting; - - #if SECURE_CLIENT != SECURE_CLIENT_NONE - const bool secure = getSetting("mqttUseSSL", 1 == MQTT_SSL_ENABLED); - #else - const bool secure = false; - #endif - - #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT - _mqttSetupAsyncClient(secure); - #elif (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT) - if (_mqttSetupSyncClient(secure) && _mqttConnectSyncClient(secure)) { - _mqttOnConnect(); - } else { - DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n")); - _mqttOnDisconnect(); - } - #else - #error "please check that MQTT_LIBRARY is valid" - #endif - -} - void _mqttPlaceholders(String& text) { text.replace("{hostname}", getSetting("hostname")); @@ -1014,6 +964,86 @@ void mqttSendStatus() { // Initialization // ----------------------------------------------------------------------------- +void _mqttConnect() { + + // Do not connect if disabled + if (!_mqtt_enabled) return; + + // Do not connect if already connected or still trying to connect + if (_mqtt.connected() || (_mqtt_state != AsyncClientState::Disconnected)) return; + + // Check reconnect interval + if (millis() - _mqtt_last_connection < _mqtt_reconnect_delay) return; + + // Increase the reconnect delay + _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP; + if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) { + _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX; + } + + #if MDNS_CLIENT_SUPPORT + _mqtt_server = mdnsResolve(_mqtt_server); + #endif + + DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%u\n"), _mqtt_server.c_str(), _mqtt_port); + + DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid.c_str()); + DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos); + DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0); + DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive); + DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will.c_str()); + + _mqtt_state = AsyncClientState::Connecting; + + #if SECURE_CLIENT != SECURE_CLIENT_NONE + const bool secure = getSetting("mqttUseSSL", 1 == MQTT_SSL_ENABLED); + #else + const bool secure = false; + #endif + + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + _mqttSetupAsyncClient(secure); + #elif (MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT) || (MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT) + if (_mqttSetupSyncClient(secure) && _mqttConnectSyncClient(secure)) { + _mqttOnConnect(); + } else { + DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n")); + _mqttOnDisconnect(); + } + #else + #error "please check that MQTT_LIBRARY is valid" + #endif + +} + +void mqttLoop() { + + if (WiFi.status() != WL_CONNECTED) return; + + #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + + _mqttConnect(); + + #else // MQTT_LIBRARY != MQTT_LIBRARY_ASYNCMQTTCLIENT + + if (_mqtt.connected()) { + + _mqtt.loop(); + + } else { + + if (_mqtt_state != AsyncClientState::Disconnected) { + _mqttOnDisconnect(); + } + + _mqttConnect(); + + } + + #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT + +} + void mqttSetup() { _mqttBackwards(); @@ -1132,38 +1162,4 @@ void mqttSetup() { } -void mqttLoop() { - - if (WiFi.status() != WL_CONNECTED) return; - - #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT - - _mqttConnect(); - - #else // MQTT_LIBRARY != MQTT_LIBRARY_ASYNCMQTTCLIENT - - if (_mqtt.connected()) { - - _mqtt.loop(); - - } else { - - if (_mqtt_state != AsyncClientState::Disconnected) { - _mqttOnDisconnect(); - } - - _mqttConnect(); - - } - - #endif // MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT - -} - -#else - -bool mqttForward() { - return false; -} - #endif // MQTT_SUPPORT diff --git a/code/espurna/mqtt.h b/code/espurna/mqtt.h index 1fc73461..b4ba6359 100644 --- a/code/espurna/mqtt.h +++ b/code/espurna/mqtt.h @@ -9,6 +9,8 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot #pragma once +#include "espurna.h" + #include #include @@ -17,9 +19,6 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot using mqtt_callback_f = std::function; using mqtt_msg_t = std::pair; // topic, payload -// TODO: need this prototype for .ino -class AsyncMqttClientMessageProperties; - #if MQTT_SUPPORT #if MQTT_LIBRARY == MQTT_LIBRARY_ASYNCMQTTCLIENT @@ -45,13 +44,37 @@ void mqttSend(const char * topic, const char * message, bool force, bool retain) void mqttSend(const char * topic, const char * message, bool force); void mqttSend(const char * topic, const char * message); +void mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain); void mqttSend(const char * topic, unsigned int index, const char * message, bool force); void mqttSend(const char * topic, unsigned int index, const char * message); +void mqttSendStatus(); +void mqttFlush(); + +int8_t mqttEnqueue(const char * topic, const char * message, unsigned char parent); +int8_t mqttEnqueue(const char * topic, const char * message); + const String& mqttPayloadOnline(); const String& mqttPayloadOffline(); const char* mqttPayloadStatus(bool status); -void mqttSendStatus(); +void mqttSetBroker(IPAddress ip, uint16_t port); +void mqttSetBrokerIfNone(IPAddress ip, uint16_t port); + +void mqttSubscribeRaw(const char * topic); +void mqttSubscribe(const char * topic); + +void mqttUnsubscribeRaw(const char * topic); +void mqttUnsubscribe(const char * topic); + +void mqttEnabled(bool status); +bool mqttEnabled(); + +bool mqttForward(); + +bool mqttConnected(); + +void mqttDisconnect(); +void mqttSetup(); #endif // MQTT_SUPPORT == 1 diff --git a/code/espurna/netbios.ino b/code/espurna/netbios.cpp similarity index 93% rename from code/espurna/netbios.ino rename to code/espurna/netbios.cpp index 83d05f04..77e1958b 100644 --- a/code/espurna/netbios.ino +++ b/code/espurna/netbios.cpp @@ -6,9 +6,9 @@ Copyright (C) 2017-2019 by Xose Pérez */ -#if NETBIOS_SUPPORT +#include "netbios.h" -#include +#if NETBIOS_SUPPORT void netbiosSetup() { static WiFiEventHandler _netbios_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) { diff --git a/code/espurna/netbios.h b/code/espurna/netbios.h new file mode 100644 index 00000000..9ba0c38d --- /dev/null +++ b/code/espurna/netbios.h @@ -0,0 +1,17 @@ +/* + +NETBIOS MODULE + +Copyright (C) 2017-2019 by Xose Pérez + +*/ + +#include "espurna.h" + +#if NETBIOS_SUPPORT + +#include + +void netbiosSetup(); + +#endif // NETBIOS_SUPPORT diff --git a/code/espurna/nofuss.ino b/code/espurna/nofuss.cpp similarity index 97% rename from code/espurna/nofuss.ino rename to code/espurna/nofuss.cpp index 842db098..4075c0bb 100644 --- a/code/espurna/nofuss.ino +++ b/code/espurna/nofuss.cpp @@ -6,9 +6,13 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "nofuss.h" + #if NOFUSS_SUPPORT -#include "NoFUSSClient.h" +#include "wifi.h" +#include "mdns.h" +#include "terminal.h" #include "ws.h" unsigned long _nofussLastCheck = 0; @@ -73,6 +77,23 @@ void _nofussConfigure() { } +// ----------------------------------------------------------------------------- + +void nofussRun() { + NoFUSSClient.handle(); + _nofussLastCheck = millis(); +} + +void _nofussLoop() { + + if (!_nofussEnabled) return; + if (!wifiConnected()) return; + if ((_nofussLastCheck > 0) && ((millis() - _nofussLastCheck) < _nofussInterval)) return; + + nofussRun(); + +} + #if TERMINAL_SUPPORT void _nofussInitCommands() { @@ -86,13 +107,6 @@ void _nofussInitCommands() { #endif // TERMINAL_SUPPORT -// ----------------------------------------------------------------------------- - -void nofussRun() { - NoFUSSClient.handle(); - _nofussLastCheck = millis(); -} - void nofussSetup() { _nofussConfigure(); @@ -177,19 +191,9 @@ void nofussSetup() { #endif // Main callbacks - espurnaRegisterLoop(nofussLoop); + espurnaRegisterLoop(_nofussLoop); espurnaRegisterReload(_nofussConfigure); } -void nofussLoop() { - - if (!_nofussEnabled) return; - if (!wifiConnected()) return; - if ((_nofussLastCheck > 0) && ((millis() - _nofussLastCheck) < _nofussInterval)) return; - - nofussRun(); - -} - #endif // NOFUSS_SUPPORT diff --git a/code/espurna/nofuss.h b/code/espurna/nofuss.h new file mode 100644 index 00000000..54aa5f9a --- /dev/null +++ b/code/espurna/nofuss.h @@ -0,0 +1,17 @@ +/* + +NOFUSS MODULE + +Copyright (C) 2016-2019 by Xose Pérez + +*/ + +#include "espurna.h" + +#if NOFUSS_SUPPORT + +#include + +void nofussSetup(); + +#endif // NOFUSS_SUPPORT diff --git a/code/espurna/ntp.ino b/code/espurna/ntp.cpp similarity index 99% rename from code/espurna/ntp.ino rename to code/espurna/ntp.cpp index 93689453..dd86047d 100644 --- a/code/espurna/ntp.ino +++ b/code/espurna/ntp.cpp @@ -11,6 +11,8 @@ Copyright (C) 2019 by Maxim Prokhorov */ +#include "ntp.h" + #if NTP_SUPPORT && !NTP_LEGACY_SUPPORT #include @@ -26,7 +28,6 @@ static_assert( #include "debug.h" #include "broker.h" #include "ws.h" -#include "ntp.h" // Arduino/esp8266 lwip2 custom functions that can be redefined // Must return time in milliseconds, legacy settings are in seconds. @@ -258,10 +259,7 @@ String ntpDateTime() { #if BROKER_SUPPORT -// XXX: Nonos docs for some reason mention 100 micros as minimum time. Schedule next second in case this is 0 -void _ntpBrokerSchedule(int offset) { - _ntp_broker_timer.once_scheduled(offset ?: 1, _ntpBrokerCallback); -} +void _ntpBrokerSchedule(int offset); void _ntpBrokerCallback() { @@ -303,6 +301,11 @@ void _ntpBrokerCallback() { } +// XXX: Nonos docs for some reason mention 100 micros as minimum time. Schedule next second in case this is 0 +void _ntpBrokerSchedule(int offset) { + _ntp_broker_timer.once_scheduled(offset ?: 1, _ntpBrokerCallback); +} + #endif void _ntpSetTimeOfDayCallback() { diff --git a/code/espurna/ntp.h b/code/espurna/ntp.h index 3e8177c3..7deed613 100644 --- a/code/espurna/ntp.h +++ b/code/espurna/ntp.h @@ -6,17 +6,19 @@ NTP MODULE #pragma once -#include "broker.h" - -// TODO: need this prototype for .ino -struct NtpCalendarWeekday; +#include "espurna.h" #if NTP_SUPPORT +#include "broker.h" + #if NTP_LEGACY_SUPPORT // Use legacy TimeLib and NtpClientLib #include -#include "libs/NtpClientWrap.h" +#include +#include + +time_t ntpLocal2UTC(time_t local); #else // POSIX time functions + configTime(...) @@ -44,8 +46,10 @@ struct NtpCalendarWeekday { using NtpBroker = TBroker; +String ntpDateTime(tm* timestruct); String ntpDateTime(time_t ts); String ntpDateTime(); +bool ntpSynced(); void ntpSetup(); diff --git a/code/espurna/ntp_legacy.ino b/code/espurna/ntp_legacy.cpp similarity index 91% rename from code/espurna/ntp_legacy.ino rename to code/espurna/ntp_legacy.cpp index c057cc83..98b58a1e 100644 --- a/code/espurna/ntp_legacy.ino +++ b/code/espurna/ntp_legacy.cpp @@ -6,13 +6,15 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "ntp.h" + #if NTP_LEGACY_SUPPORT && NTP_SUPPORT #include +#include "debug.h" #include "broker.h" #include "ws.h" -#include "ntp.h" Ticker _ntp_defer; @@ -20,6 +22,31 @@ bool _ntp_report = false; bool _ntp_configure = false; bool _ntp_want_sync = false; +// ----------------------------------------------------------------------------- +// NtpClient overrides to avoid triggering network sync +// ----------------------------------------------------------------------------- + +class NTPClientWrap : public NTPClient { + +public: + + NTPClientWrap() : NTPClient() { + udp = new WiFiUDP(); + _lastSyncd = 0; + } + + bool setInterval(int shortInterval, int longInterval) { + _shortInterval = shortInterval; + _longInterval = longInterval; + return true; + } + +}; + +// NOTE: original NTP should be discarded by the linker +// TODO: allow NTP client object to be destroyed +static NTPClientWrap NTPw; + // ----------------------------------------------------------------------------- // NTP // ----------------------------------------------------------------------------- @@ -67,24 +94,6 @@ int _ntpUpdateInterval() { return secureRandom(NTP_UPDATE_INTERVAL, NTP_UPDATE_INTERVAL * 2); } -void _ntpStart() { - - _ntpConfigure(); - - // short (initial) and long (after sync) intervals - NTPw.setInterval(_ntpSyncInterval(), _ntpUpdateInterval()); - DEBUG_MSG_P(PSTR("[NTP] Update intervals: %us / %us\n"), - NTPw.getShortInterval(), NTPw.getLongInterval()); - - // setSyncProvider will immediatly call given function by setting next sync time to the current time. - // Avoid triggering sync immediatly by canceling sync provider flag and resetting sync interval again - setSyncProvider(_ntpSyncProvider); - _ntp_want_sync = false; - - setSyncInterval(NTPw.getShortInterval()); - -} - void _ntpConfigure() { _ntp_configure = false; @@ -119,6 +128,25 @@ void _ntpConfigure() { } +void _ntpStart() { + + _ntpConfigure(); + + // short (initial) and long (after sync) intervals + NTPw.setInterval(_ntpSyncInterval(), _ntpUpdateInterval()); + DEBUG_MSG_P(PSTR("[NTP] Update intervals: %us / %us\n"), + NTPw.getShortInterval(), NTPw.getLongInterval()); + + // setSyncProvider will immediatly call given function by setting next sync time to the current time. + // Avoid triggering sync immediatly by canceling sync provider flag and resetting sync interval again + setSyncProvider(_ntpSyncProvider); + _ntp_want_sync = false; + + setSyncInterval(NTPw.getShortInterval()); + +} + + void _ntpReport() { _ntp_report = false; diff --git a/code/espurna/ota.ino b/code/espurna/ota.cpp similarity index 100% rename from code/espurna/ota.ino rename to code/espurna/ota.cpp diff --git a/code/espurna/ota.h b/code/espurna/ota.h index edc1000d..713100f7 100644 --- a/code/espurna/ota.h +++ b/code/espurna/ota.h @@ -6,8 +6,10 @@ OTA MODULE #pragma once -#include +#include "espurna.h" + #include +#include #if OTA_WEB_SUPPORT diff --git a/code/espurna/ota_arduinoota.ino b/code/espurna/ota_arduinoota.cpp similarity index 99% rename from code/espurna/ota_arduinoota.ino rename to code/espurna/ota_arduinoota.cpp index 3767760d..8a32ae71 100644 --- a/code/espurna/ota_arduinoota.ino +++ b/code/espurna/ota_arduinoota.cpp @@ -6,9 +6,10 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "ota.h" + #if OTA_ARDUINOOTA_SUPPORT -#include "ota.h" #include "system.h" #include "ws.h" diff --git a/code/espurna/ota_asynctcp.ino b/code/espurna/ota_asynctcp.cpp similarity index 99% rename from code/espurna/ota_asynctcp.ino rename to code/espurna/ota_asynctcp.cpp index cd55bfe4..8642e1a7 100644 --- a/code/espurna/ota_asynctcp.ino +++ b/code/espurna/ota_asynctcp.cpp @@ -6,20 +6,25 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "ota.h" + #if OTA_CLIENT == OTA_CLIENT_ASYNCTCP // ----------------------------------------------------------------------------- // Terminal and MQTT OTA command handlers // ----------------------------------------------------------------------------- +#include +#include "espurna.h" + #if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT #include #include #include "mqtt.h" -#include "ota.h" #include "system.h" +#include "settings.h" #include "terminal.h" #include "libs/URL.h" diff --git a/code/espurna/ota_httpupdate.ino b/code/espurna/ota_httpupdate.cpp similarity index 99% rename from code/espurna/ota_httpupdate.ino rename to code/espurna/ota_httpupdate.cpp index a16baf6d..17af80a5 100644 --- a/code/espurna/ota_httpupdate.ino +++ b/code/espurna/ota_httpupdate.cpp @@ -10,12 +10,13 @@ Copyright (C) 2019 by Maxim Prokhorov // OTA by using Core's HTTP(s) updater // ----------------------------------------------------------------------------- +#include "ota.h" + #if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE #include #include "mqtt.h" -#include "ota.h" #include "system.h" #include "terminal.h" diff --git a/code/espurna/ota_web.ino b/code/espurna/ota_web.cpp similarity index 98% rename from code/espurna/ota_web.ino rename to code/espurna/ota_web.cpp index 09271cce..ffb1fd4b 100644 --- a/code/espurna/ota_web.ino +++ b/code/espurna/ota_web.cpp @@ -8,14 +8,12 @@ Copyright (C) 2020 by Maxim Prokhorov */ #include "ota.h" -#include "settings.h" -#include "storage_eeprom.h" -#include "utils.h" -#include "web.h" -#include "ws.h" #if WEB_SUPPORT && OTA_WEB_SUPPORT +#include "web.h" +#include "ws.h" + void _onUpgradeResponse(AsyncWebServerRequest *request, int code, const String& payload = "") { auto *response = request->beginResponseStream("text/plain", 256); diff --git a/code/espurna/relay.ino b/code/espurna/relay.cpp similarity index 96% rename from code/espurna/relay.ino rename to code/espurna/relay.cpp index 92155bc7..50dc5686 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.cpp @@ -6,6 +6,8 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "relay.h" + #if RELAY_SUPPORT #include @@ -14,13 +16,17 @@ Copyright (C) 2016-2019 by Xose Pérez #include #include +#include "api.h" #include "broker.h" -#include "storage_eeprom.h" -#include "settings.h" +#include "light.h" #include "mqtt.h" -#include "relay.h" +#include "rfbridge.h" #include "rpc.h" +#include "rtcmem.h" +#include "settings.h" +#include "storage_eeprom.h" #include "tuya.h" +#include "utils.h" #include "ws.h" #include "relay_config.h" @@ -423,9 +429,38 @@ void setSpeed(unsigned char speed) { // ----------------------------------------------------------------------------- // State persistance persistance +namespace { + +String u32toString(uint32_t value, int base) { + String result; + result.reserve(32 + 2); + + if (base == 2) { + result += "0b"; + } else if (base == 8) { + result += "0o"; + } else if (base == 16) { + result += "0x"; + } + + char buffer[33] = {0}; + ultoa(value, buffer, base); + result += buffer; + + return result; +} + +struct RelayMask { + const String as_string; + uint32_t as_u32; +}; + +RelayMask INLINE _relayMask(uint32_t mask) { + return {std::move(u32toString(mask, 2)), mask}; +} RelayMask INLINE _relayMaskRtcmem() { - return RelayMask(Rtcmem->relay); + return _relayMask(Rtcmem->relay); } void INLINE _relayMaskRtcmem(uint32_t mask) { @@ -441,7 +476,9 @@ void INLINE _relayMaskRtcmem(const std::bitset& bitset) { } RelayMask INLINE _relayMaskSettings() { - return RelayMask(getSetting("relayBootMask")); + constexpr unsigned long defaultMask { 0ul }; + auto value = getSetting("relayBootMask", defaultMask); + return _relayMask(value); } void INLINE _relayMaskSettings(uint32_t mask) { @@ -456,6 +493,8 @@ void INLINE _relayMaskSettings(const std::bitset& bitset) { _relayMaskSettings(bitset.to_ulong()); } +} // ns anonymous + // Pulse timers (timer after ON or OFF event) void relayPulse(unsigned char id) { @@ -563,7 +602,11 @@ bool relayStatus(unsigned char id, bool status, bool report, bool group_report) } bool relayStatus(unsigned char id, bool status) { - return relayStatus(id, status, mqttForward(), true); + #if MQTT_SUPPORT + return relayStatus(id, status, mqttForward(), true); + #else + return relayStatus(id, status, false, true); + #endif } bool relayStatus(unsigned char id) { @@ -641,7 +684,7 @@ void relaySave(bool eeprom) { statuses.set(id, relayStatus(id)); } - const RelayMask mask(statuses); + const auto mask = _relayMask(statuses.to_ulong() & 0xffffffffu); 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 @@ -672,7 +715,11 @@ void relayToggle(unsigned char id, bool report, bool group_report) { } void relayToggle(unsigned char id) { - relayToggle(id, mqttForward(), true); + #if MQTT_SUPPORT + relayToggle(id, mqttForward(), true); + #else + relayToggle(id, false, true); + #endif } unsigned char relayCount() { @@ -781,7 +828,7 @@ void _relayBoot() { _relayRecursive = false; #if TUYA_SUPPORT - tuyaSyncSwitchStatus(); + Tuya::tuyaSyncSwitchStatus(); #endif } @@ -1114,12 +1161,17 @@ void relayMQTT() { } void relayStatusWrap(unsigned char id, PayloadStatus value, bool is_group_topic) { + #if MQTT_SUPPORT + const auto forward = mqttForward(); + #else + const auto forward = false; + #endif switch (value) { case PayloadStatus::Off: - relayStatus(id, false, mqttForward(), !is_group_topic); + relayStatus(id, false, forward, !is_group_topic); break; case PayloadStatus::On: - relayStatus(id, true, mqttForward(), !is_group_topic); + relayStatus(id, true, forward, !is_group_topic); break; case PayloadStatus::Toggle: relayToggle(id, true, true); @@ -1270,7 +1322,7 @@ void _relaySetupProvider() { // note of the function call order! relay code is initialized before tuya's, and the easiest // way to accomplish that is to use ctor as a way to "register" callbacks even before setup() is called #if TUYA_SUPPORT - tuyaSetupSwitch(); + Tuya::tuyaSetupSwitch(); #endif } diff --git a/code/espurna/relay.h b/code/espurna/relay.h index c2bc051c..2a462a9e 100644 --- a/code/espurna/relay.h +++ b/code/espurna/relay.h @@ -8,43 +8,13 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once -#include +#include "espurna.h" #include "rpc.h" -#include "utils.h" + +#include constexpr size_t RELAYS_MAX = 32; -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; - -}; - PayloadStatus relayParsePayload(const char * payload); bool relayStatus(unsigned char id, bool status, bool report, bool group_report); @@ -62,5 +32,12 @@ const String& relayPayloadToggle(); const char* relayPayload(PayloadStatus status); -void relaySetupDummy(size_t size, bool reconfigure = false); +void relayMQTT(unsigned char id); +void relayMQTT(); +void relayPulse(unsigned char id); +void relaySync(unsigned char id); +void relaySave(bool eeprom); + +void relaySetupDummy(size_t size, bool reconfigure = false); +void relaySetup(); diff --git a/code/espurna/relay_config.h b/code/espurna/relay_config.h index 5254b5da..784c35a9 100644 --- a/code/espurna/relay_config.h +++ b/code/espurna/relay_config.h @@ -6,6 +6,8 @@ RELAY MODULE #pragma once +#include "espurna.h" + constexpr const unsigned long _relayDelayOn(unsigned char index) { return ( (index == 0) ? RELAY1_DELAY_ON : diff --git a/code/espurna/rfbridge.ino b/code/espurna/rfbridge.cpp similarity index 94% rename from code/espurna/rfbridge.ino rename to code/espurna/rfbridge.cpp index 6cf5e5a3..6509c55e 100644 --- a/code/espurna/rfbridge.ino +++ b/code/espurna/rfbridge.cpp @@ -6,12 +6,14 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "rfbridge.h" + #if RF_SUPPORT #include +#include "api.h" #include "relay.h" -#include "rfbridge.h" #include "terminal.h" #include "mqtt.h" #include "ws.h" @@ -55,10 +57,10 @@ unsigned char _learnId = 0; bool _learnStatus = true; bool _rfbin = false; -typedef struct { - byte code[RF_MESSAGE_SIZE]; - byte times; -} rfb_message_t; +struct rfb_message_t { + uint8_t code[RF_MESSAGE_SIZE]; + uint8_t times; +}; static std::queue _rfb_message_queue; #if RFB_DIRECT @@ -74,10 +76,8 @@ unsigned char _rfb_repeat = RF_SEND_TIMES; // PRIVATES // ----------------------------------------------------------------------------- -/* - From a byte array to an hexa char array ("A220EE...", double the size) - */ -static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) { +// From a byte array to an hexa char array ("A220EE...", double the size) +static bool _rfbToChar(uint8_t * in, char * out, int n = RF_MESSAGE_SIZE) { for (unsigned char p = 0; p RF_MAX_MESSAGE_SIZE*2 || (length > 0 && n != length)) return 0; char tmp[3] = {0,0,0}; @@ -145,64 +143,10 @@ static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE return n; } -void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) { - for (unsigned char j=0; j 1) { - message.times = message.times - 1; - _rfb_message_queue.push(message); - } - - yield(); - -} - -void _rfbSend(byte * code, unsigned char times) { - - if (!_rfb_transmit) return; - - // rc-switch will repeat on its own - #if RFB_DIRECT - times = 1; - #endif - - char buffer[RF_MESSAGE_SIZE]; - _rfbToChar(code, buffer); - DEBUG_MSG_P(PSTR("[RF] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times); - - rfb_message_t message; - memcpy(message.code, code, RF_MESSAGE_SIZE); - message.times = times; - _rfb_message_queue.push(message); - -} - -void _rfbSendRawOnce(byte *code, unsigned char length) { - char buffer[length*2]; - _rfbToChar(code, buffer, length); - DEBUG_MSG_P(PSTR("[RF] Sending RAW MESSAGE '%s'\n"), buffer); - _rfbSendRaw(code, length); -} +void _rfbAck(); +void _rfbLearnImpl(); +void _rfbSend(uint8_t * message); +void _rfbReceive(); bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* buffer = NULL) { @@ -248,7 +192,7 @@ void _rfbDecode() { if (millis() - last < RF_RECEIVE_DELAY) return; last = millis(); - byte action = _uartbuf[0]; + uint8_t action = _uartbuf[0]; char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0}; DEBUG_MSG_P(PSTR("[RF] Action 0x%02X\n"), action); @@ -308,50 +252,6 @@ void _rfbDecode() { } -bool _rfbCompare(const char * code1, const char * code2) { - return strcmp(&code1[12], &code2[12]) == 0; -} - -bool _rfbSameOnOff(unsigned char id) { - return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str()); -} - -void _rfbParseRaw(char * raw) { - byte message[RF_MAX_MESSAGE_SIZE]; - int len = _rfbToArray(raw, message, 0); - if (len > 0) { - _rfbSendRawOnce(message, len); - } -} - -void _rfbParseCode(char * code) { - - // The payload may be a code in HEX format ([0-9A-Z]{18}) or - // the code comma the number of times to transmit it. - char * tok = strtok(code, ","); - - // Check if a switch is linked to that message - unsigned char id; - unsigned char status = 0; - if (_rfbMatch(tok, id, status)) { - if (status == 2) { - relayToggle(id); - } else { - relayStatus(id, status == 1); - } - return; - } - - byte message[RF_MESSAGE_SIZE]; - int len = _rfbToArray(tok, message, 0); - if (len) { - tok = strtok(NULL, ","); - byte times = (tok != NULL) ? atoi(tok) : 1; - _rfbSend(message, times); - } - -} - // // RF handler implementations @@ -379,7 +279,7 @@ void _rfbLearnImpl() { Serial.println(); } -void _rfbSend(byte * message) { +void _rfbSend(uint8_t * message) { Serial.println(); Serial.write(RF_CODE_START); Serial.write(RF_CODE_RFOUT); @@ -396,7 +296,7 @@ void _rfbReceive() { while (Serial.available()) { yield(); - byte c = Serial.read(); + uint8_t c = Serial.read(); //DEBUG_MSG_P(PSTR("[RF] Received 0x%02X\n"), c); if (receiving) { @@ -427,7 +327,7 @@ void _rfbLearnImpl() { _learning = true; } -void _rfbSend(byte * message) { +void _rfbSend(uint8_t * message) { if (!_rfb_transmit) return; @@ -505,6 +405,109 @@ void _rfbReceive() { #endif // RFB_DIRECT +void _rfbSendRaw(const uint8_t *message, const unsigned char n = RF_MESSAGE_SIZE) { + for (unsigned char j=0; j 1) { + message.times = message.times - 1; + _rfb_message_queue.push(message); + } + + yield(); + +} + +void _rfbSendRawOnce(uint8_t *code, unsigned char length) { + char buffer[length*2]; + _rfbToChar(code, buffer, length); + DEBUG_MSG_P(PSTR("[RF] Sending RAW MESSAGE '%s'\n"), buffer); + _rfbSendRaw(code, length); +} + +bool _rfbCompare(const char * code1, const char * code2) { + return strcmp(&code1[12], &code2[12]) == 0; +} + +bool _rfbSameOnOff(unsigned char id) { + return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str()); +} + +void _rfbParseRaw(char * raw) { + uint8_t message[RF_MAX_MESSAGE_SIZE]; + int len = _rfbToArray(raw, message, 0); + if (len > 0) { + _rfbSendRawOnce(message, len); + } +} + +void _rfbParseCode(char * code) { + + // The payload may be a code in HEX format ([0-9A-Z]{18}) or + // the code comma the number of times to transmit it. + char * tok = strtok(code, ","); + + // Check if a switch is linked to that message + unsigned char id; + unsigned char status = 0; + if (_rfbMatch(tok, id, status)) { + if (status == 2) { + relayToggle(id); + } else { + relayStatus(id, status == 1); + } + return; + } + + uint8_t message[RF_MESSAGE_SIZE]; + int len = _rfbToArray(tok, message, 0); + if (len) { + tok = strtok(NULL, ","); + uint8_t times = (tok != NULL) ? atoi(tok) : 1; + _rfbSend(message, times); + } + +} + #if MQTT_SUPPORT void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) { @@ -687,7 +690,7 @@ void rfbStatus(unsigned char id, bool status) { bool same = _rfbSameOnOff(id); - byte message[RF_MAX_MESSAGE_SIZE]; + uint8_t message[RF_MAX_MESSAGE_SIZE]; int len = _rfbToArray(value.c_str(), message, 0); if (len == RF_MESSAGE_SIZE && // probably a standard msg @@ -788,13 +791,11 @@ void rfbSetup() { #endif // Register loop only when properly configured - espurnaRegisterLoop(rfbLoop); + espurnaRegisterLoop([]() -> void { + _rfbReceive(); + _rfbSend(); + }); } -void rfbLoop() { - _rfbReceive(); - _rfbSend(); -} - #endif diff --git a/code/espurna/rfbridge.h b/code/espurna/rfbridge.h index efc355bc..23c903b0 100644 --- a/code/espurna/rfbridge.h +++ b/code/espurna/rfbridge.h @@ -6,6 +6,8 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "espurna.h" + #if RF_SUPPORT #if RFB_DIRECT diff --git a/code/espurna/rfm69.ino b/code/espurna/rfm69.cpp similarity index 87% rename from code/espurna/rfm69.ino rename to code/espurna/rfm69.cpp index ac6c4746..ccb73d37 100644 --- a/code/espurna/rfm69.ino +++ b/code/espurna/rfm69.cpp @@ -6,10 +6,11 @@ Copyright (C) 2016-2017 by Xose Pérez */ +#include "rfm69.h" + #if RFM69_SUPPORT #include "mqtt.h" -#include "rfm69.h" #include "ws.h" #define RFM69_PACKET_SEPARATOR ':' @@ -18,7 +19,15 @@ Copyright (C) 2016-2017 by Xose Pérez // Locals // ----------------------------------------------------------------------------- -RFM69Wrap * _rfm69_radio; +struct packet_t { + unsigned long messageID; + unsigned char packetID; + unsigned char senderID; + unsigned char targetID; + char * key; + char * value; + int16_t rssi; +}; struct _node_t { unsigned long count = 0; @@ -31,6 +40,58 @@ _node_t _rfm69_node_info[RFM69_MAX_NODES]; unsigned char _rfm69_node_count; unsigned long _rfm69_packet_count; +void _rfm69Clear() { + for(unsigned int i=0; i #pragma once -struct packet_t; +#include "espurna.h" #if RFM69_SUPPORT -#include "libs/RFM69Wrap.h" - -struct packet_t { - unsigned long messageID; - unsigned char packetID; - unsigned char senderID; - unsigned char targetID; - char * key; - char * value; - int16_t rssi; -}; +#include +#include +#include void rfm69Setup(); diff --git a/code/espurna/rpc.ino b/code/espurna/rpc.cpp similarity index 100% rename from code/espurna/rpc.ino rename to code/espurna/rpc.cpp diff --git a/code/espurna/rpc.h b/code/espurna/rpc.h index 958db22f..aa88c6e6 100644 --- a/code/espurna/rpc.h +++ b/code/espurna/rpc.h @@ -6,6 +6,8 @@ Part of MQTT and API modules #pragma once +#include "espurna.h" + #include #include diff --git a/code/espurna/rpnrules.ino b/code/espurna/rpnrules.cpp similarity index 99% rename from code/espurna/rpnrules.ino rename to code/espurna/rpnrules.cpp index f9ca9486..e405079f 100644 --- a/code/espurna/rpnrules.ino +++ b/code/espurna/rpnrules.cpp @@ -6,11 +6,16 @@ Copyright (C) 2019 by Xose Pérez */ +#include "rpnrules.h" + #if RPN_RULES_SUPPORT +#include "broker.h" +#include "mqtt.h" #include "ntp.h" #include "relay.h" -#include "rpnrules.h" +#include "terminal.h" +#include "ws.h" // ----------------------------------------------------------------------------- // Custom commands @@ -132,6 +137,21 @@ bool _rpnNtpFunc(rpn_context & ctxt, int (*func)(time_t)) { #endif +void _rpnDump() { + float value; + DEBUG_MSG_P(PSTR("[RPN] Stack:\n")); + unsigned char num = rpn_stack_size(_rpn_ctxt); + if (0 == num) { + DEBUG_MSG_P(PSTR(" (empty)\n")); + } else { + unsigned char index = num - 1; + while (rpn_stack_get(_rpn_ctxt, index, value)) { + DEBUG_MSG_P(PSTR(" %02d: %s\n"), index--, String(value).c_str()); + } + } +} + + void _rpnInit() { // Init context @@ -300,20 +320,6 @@ void _rpnInitCommands() { } #endif -void _rpnDump() { - float value; - DEBUG_MSG_P(PSTR("[RPN] Stack:\n")); - unsigned char num = rpn_stack_size(_rpn_ctxt); - if (0 == num) { - DEBUG_MSG_P(PSTR(" (empty)\n")); - } else { - unsigned char index = num - 1; - while (rpn_stack_get(_rpn_ctxt, index, value)) { - DEBUG_MSG_P(PSTR(" %02d: %s\n"), index--, String(value).c_str()); - } - } -} - void _rpnRun() { unsigned char i = 0; diff --git a/code/espurna/rpnrules.h b/code/espurna/rpnrules.h index ae7331de..baaaca91 100644 --- a/code/espurna/rpnrules.h +++ b/code/espurna/rpnrules.h @@ -8,8 +8,7 @@ Copyright (C) 2019 by Xose Pérez #pragma once -// TODO: need this prototype for .ino -struct rpn_context; +#include "espurna.h" #if RPN_RULES_SUPPORT diff --git a/code/espurna/rtcmem.ino b/code/espurna/rtcmem.cpp similarity index 95% rename from code/espurna/rtcmem.ino rename to code/espurna/rtcmem.cpp index c7d09758..3b804fa2 100644 --- a/code/espurna/rtcmem.ino +++ b/code/espurna/rtcmem.cpp @@ -6,6 +6,10 @@ Copyright (C) 2019 by Maxim Prokhorov */ +#include "rtcmem.h" + +volatile RtcmemData* Rtcmem = reinterpret_cast(RTCMEM_ADDR); + bool _rtcmem_status = false; void _rtcmemErase() { diff --git a/code/espurna/rtcmem.h b/code/espurna/rtcmem.h index f1e6fca7..10d727ed 100644 --- a/code/espurna/rtcmem.h +++ b/code/espurna/rtcmem.h @@ -8,6 +8,11 @@ Copyright (C) 2019 by Maxim Prokhorov #pragma once +#include +#include + +#include "espurna.h" + // Base address of USER RTC memory // https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map#memmory-mapped-io-registers #define RTCMEM_ADDR_BASE (0x60001200) @@ -56,6 +61,7 @@ struct RtcmemData { static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); constexpr uint8_t RtcmemSize = (sizeof(RtcmemData) / 4u); -auto Rtcmem = reinterpret_cast(RTCMEM_ADDR); +extern volatile RtcmemData* Rtcmem; bool rtcmemStatus(); +void rtcmemSetup(); diff --git a/code/espurna/scheduler.ino b/code/espurna/scheduler.cpp similarity index 99% rename from code/espurna/scheduler.ino rename to code/espurna/scheduler.cpp index aa41fb0e..fa3e94ca 100644 --- a/code/espurna/scheduler.ino +++ b/code/espurna/scheduler.cpp @@ -7,11 +7,15 @@ Adapted by Xose Pérez */ +#include "scheduler.h" + #if SCHEDULER_SUPPORT #include "broker.h" -#include "relay.h" +#include "light.h" #include "ntp.h" +#include "relay.h" +#include "ws.h" constexpr const int SchedulerDummySwitchId = 0xff; diff --git a/code/espurna/scheduler.h b/code/espurna/scheduler.h new file mode 100644 index 00000000..78d6a52b --- /dev/null +++ b/code/espurna/scheduler.h @@ -0,0 +1,14 @@ +/* + +SCHEDULER MODULE + +Copyright (C) 2017 by faina09 +Adapted by Xose Pérez + +*/ + +#pragma once + +#include "espurna.h" + +void schSetup(); diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.cpp similarity index 94% rename from code/espurna/sensor.ino rename to code/espurna/sensor.cpp index 64f4ffd3..e8dc14e2 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.cpp @@ -6,20 +6,202 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "sensor.h" + #if SENSOR_SUPPORT #include #include +#include "api.h" #include "broker.h" #include "domoticz.h" +#include "i2c.h" #include "mqtt.h" #include "ntp.h" #include "relay.h" -#include "sensor.h" #include "terminal.h" +#include "thingspeak.h" +#include "rtcmem.h" #include "ws.h" +//-------------------------------------------------------------------------------- + +// TODO: namespace { ... } ? sensor ctors need to work though + +#include "filters/LastFilter.h" +#include "filters/MaxFilter.h" +#include "filters/MedianFilter.h" +#include "filters/MovingAverageFilter.h" +#include "filters/SumFilter.h" + +#include "sensors/BaseSensor.h" +#include "sensors/BaseEmonSensor.h" +#include "sensors/BaseAnalogSensor.h" + +#if AM2320_SUPPORT + #include "sensors/AM2320Sensor.h" +#endif + +#if ANALOG_SUPPORT + #include "sensors/AnalogSensor.h" +#endif + +#if BH1750_SUPPORT + #include "sensors/BH1750Sensor.h" +#endif + +#if BMP180_SUPPORT + #include "sensors/BMP180Sensor.h" +#endif + +#if BMX280_SUPPORT + #include "sensors/BMX280Sensor.h" +#endif + +#if CSE7766_SUPPORT + #include "sensors/CSE7766Sensor.h" +#endif + +#if DALLAS_SUPPORT + #include "sensors/DallasSensor.h" +#endif + +#if DHT_SUPPORT + #include "sensors/DHTSensor.h" +#endif + +#if DIGITAL_SUPPORT + #include "sensors/DigitalSensor.h" +#endif + +#if ECH1560_SUPPORT + #include "sensors/ECH1560Sensor.h" +#endif + +#if EMON_ADC121_SUPPORT + #include "sensors/EmonADC121Sensor.h" +#endif + +#if EMON_ADS1X15_SUPPORT + #include "sensors/EmonADS1X15Sensor.h" +#endif + +#if EMON_ANALOG_SUPPORT + #include "sensors/EmonAnalogSensor.h" +#endif + +#if EVENTS_SUPPORT + #include "sensors/EventSensor.h" +#endif + +#if EZOPH_SUPPORT + #include "sensors/EZOPHSensor.h" +#endif + +#if GEIGER_SUPPORT + #include "sensors/GeigerSensor.h" +#endif + +#if GUVAS12SD_SUPPORT + #include "sensors/GUVAS12SDSensor.h" +#endif + +#if HLW8012_SUPPORT + #include "sensors/HLW8012Sensor.h" +#endif + +#if LDR_SUPPORT + #include "sensors/LDRSensor.h" +#endif + +#if MAX6675_SUPPORT + #include "sensors/MAX6675Sensor.h" +#endif + +#if MICS2710_SUPPORT + #include "sensors/MICS2710Sensor.h" +#endif + +#if MICS5525_SUPPORT + #include "sensors/MICS5525Sensor.h" +#endif + +#if MHZ19_SUPPORT + #include "sensors/MHZ19Sensor.h" +#endif + +#if NTC_SUPPORT + #include "sensors/NTCSensor.h" +#endif + +#if SDS011_SUPPORT + #include "sensors/SDS011Sensor.h" +#endif + +#if SENSEAIR_SUPPORT + #include "sensors/SenseAirSensor.h" +#endif + +#if PMSX003_SUPPORT + #include "sensors/PMSX003Sensor.h" +#endif + +#if PULSEMETER_SUPPORT + #include "sensors/PulseMeterSensor.h" +#endif + +#if PZEM004T_SUPPORT + #include "sensors/PZEM004TSensor.h" +#endif + +#if SHT3X_I2C_SUPPORT + #include "sensors/SHT3XI2CSensor.h" +#endif + +#if SI7021_SUPPORT + #include "sensors/SI7021Sensor.h" +#endif + +#if SONAR_SUPPORT + #include "sensors/SonarSensor.h" +#endif + +#if T6613_SUPPORT + #include "sensors/T6613Sensor.h" +#endif + +#if TMP3X_SUPPORT + #include "sensors/TMP3XSensor.h" +#endif + +#if V9261F_SUPPORT + #include "sensors/V9261FSensor.h" +#endif + +#if VEML6075_SUPPORT + #include "sensors/VEML6075Sensor.h" +#endif + +#if VL53L1X_SUPPORT + #include "sensors/VL53L1XSensor.h" +#endif + +#if ADE7953_SUPPORT + #include "sensors/ADE7953Sensor.h" +#endif + +#if SI1145_SUPPORT + #include "sensors/SI1145Sensor.h" +#endif + +#if HDC1080_SUPPORT + #include "sensors/HDC1080Sensor.h" +#endif + +//-------------------------------------------------------------------------------- + + struct sensor_magnitude_t { private: @@ -149,10 +331,108 @@ void Energy::reset() { } // namespace sensor +// ----------------------------------------------------------------------------- +// Energy persistence +// ----------------------------------------------------------------------------- + +std::vector _sensor_save_count; +unsigned char _sensor_save_every = SENSOR_SAVE_EVERY; + bool _sensorIsEmon(BaseSensor* sensor) { return sensor->type() & sensor::type::Emon; } +sensor::Energy _sensorRtcmemLoadEnergy(unsigned char index) { + return sensor::Energy { + sensor::KWh { Rtcmem->energy[index].kwh }, + sensor::Ws { Rtcmem->energy[index].ws } + }; +} + +void _sensorRtcmemSaveEnergy(unsigned char index, const sensor::Energy& source) { + Rtcmem->energy[index].kwh = source.kwh.value; + Rtcmem->energy[index].ws = source.ws.value; +} + +sensor::Energy _sensorParseEnergy(const String& value) { + sensor::Energy result; + + const bool separator = value.indexOf('+') > 0; + if (value.length() && (separator > 0)) { + const String before = value.substring(0, separator); + const String after = value.substring(separator + 1); + result.kwh = strtoul(before.c_str(), nullptr, 10); + result.ws = strtoul(after.c_str(), nullptr, 10); + } + + return result; +} + +void _sensorApiResetEnergy(const sensor_magnitude_t& magnitude, const char* payload) { + if (!payload || !strlen(payload)) return; + if (payload[0] != '0') return; + + auto* sensor = static_cast(magnitude.sensor); + auto energy = _sensorParseEnergy(payload); + + sensor->resetEnergy(magnitude.global, energy); +} + +sensor::Energy _sensorEnergyTotal(unsigned char index) { + + sensor::Energy result; + + if (rtcmemStatus() && (index < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy)))) { + result = _sensorRtcmemLoadEnergy(index); + } else if (_sensor_save_every > 0) { + result = _sensorParseEnergy(getSetting({"eneTotal", index})); + } + + return result; + +} + +sensor::Energy sensorEnergyTotal() { + return _sensorEnergyTotal(0); +} + +void _sensorResetEnergyTotal(unsigned char index) { + delSetting({"eneTotal", index}); + delSetting({"eneTime", index}); + if (index < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy))) { + Rtcmem->energy[index].kwh = 0; + Rtcmem->energy[index].ws = 0; + } +} + +void _magnitudeSaveEnergyTotal(sensor_magnitude_t& magnitude, bool persistent) { + if (magnitude.type != MAGNITUDE_ENERGY) return; + + auto* sensor = static_cast(magnitude.sensor); + + const auto energy = sensor->totalEnergy(); + + // Always save to RTCMEM + if (magnitude.global < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy))) { + _sensorRtcmemSaveEnergy(magnitude.global, energy); + } + + // Save to EEPROM every '_sensor_save_every' readings + // Format is `+`, value without `+` is treated as `` + if (persistent && _sensor_save_every) { + _sensor_save_count[magnitude.global] = + (_sensor_save_count[magnitude.global] + 1) % _sensor_save_every; + + if (0 == _sensor_save_count[magnitude.global]) { + const String total = String(energy.kwh.value) + "+" + String(energy.ws.value); + setSetting({"eneTotal", magnitude.global}, total); + #if NTP_SUPPORT + if (ntpSynced()) setSetting({"eneTime", magnitude.global}, ntpDateTime()); + #endif + } + } +} + // --------------------------------------------------------------------------- std::vector _sensors; @@ -163,10 +443,6 @@ bool _sensor_realtime = API_REAL_TIME_VALUES; unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL; unsigned char _sensor_report_every = SENSOR_REPORT_EVERY; -// Energy persistence -std::vector _sensor_save_count; -unsigned char _sensor_save_every = SENSOR_SAVE_EVERY; - // ----------------------------------------------------------------------------- // Private // ----------------------------------------------------------------------------- @@ -369,11 +645,11 @@ String magnitudeTopic(unsigned char type) { } -String magnitudeTopic(const sensor_magnitude_t& magnitude) { +String _magnitudeTopic(const sensor_magnitude_t& magnitude) { return magnitudeTopic(magnitude.type); } -String magnitudeUnits(const sensor_magnitude_t& magnitude) { +String _magnitudeUnits(const sensor_magnitude_t& magnitude) { const __FlashStringHelper* result = nullptr; @@ -457,7 +733,7 @@ String magnitudeUnits(const sensor_magnitude_t& magnitude) { String magnitudeUnits(unsigned char index) { if (index >= magnitudeCount()) return String(); - return magnitudeUnits(_magnitudes[index]); + return _magnitudeUnits(_magnitudes[index]); } // Choose unit based on type of magnitude we use @@ -548,11 +824,12 @@ double _magnitudeProcess(const sensor_magnitude_t& magnitude, double value) { #if WEB_SUPPORT -//void _sensorWebSocketMagnitudes(JsonObject& root, const String& ws_name, const String& conf_name) { -template void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) { +// Used by modules to generate magnitude_id<->module_id mapping for the WebUI + +void sensorWebSocketMagnitudes(JsonObject& root, const String& prefix) { // ws produces flat list Magnitudes - const String ws_name = String(prefix) + "Magnitudes"; + const String ws_name = prefix + "Magnitudes"; // config uses Magnitude (cut 's') const String conf_name = ws_name.substring(0, ws_name.length() - 1); @@ -560,33 +837,17 @@ template void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) JsonObject& list = root.createNestedObject(ws_name); list["size"] = magnitudeCount(); - //JsonArray& name = list.createNestedArray("name"); JsonArray& type = list.createNestedArray("type"); JsonArray& index = list.createNestedArray("index"); JsonArray& idx = list.createNestedArray("idx"); for (unsigned char i=0; i void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) { - - // ws produces flat list Magnitudes - const String ws_name = String(prefix) + "Magnitudes"; - - // config uses Magnitude (cut 's') - const String conf_name = ws_name.substring(0, ws_name.length() - 1); - - _sensorWebSocketMagnitudes(root, ws_name, conf_name); - -} -*/ - bool _sensorWebSocketOnKeyCheck(const char * key, JsonVariant& value) { if (strncmp(key, "pwr", 3) == 0) return true; if (strncmp(key, "sns", 3) == 0) return true; @@ -629,7 +890,7 @@ void _sensorWebSocketMagnitudesConfig(JsonObject& root) { index.add(magnitude.global); type.add(magnitude.type); - units.add(magnitudeUnits(magnitude)); + units.add(_magnitudeUnits(magnitude)); { String sensor_desc = magnitude.sensor->slot(magnitude.local); @@ -873,98 +1134,6 @@ void _sensorPost() { } } -sensor::Energy _sensorRtcmemLoadEnergy(unsigned char index) { - return sensor::Energy { - sensor::KWh { Rtcmem->energy[index].kwh }, - sensor::Ws { Rtcmem->energy[index].ws } - }; -} - -void _sensorRtcmemSaveEnergy(unsigned char index, const sensor::Energy& source) { - Rtcmem->energy[index].kwh = source.kwh.value; - Rtcmem->energy[index].ws = source.ws.value; -} - -sensor::Energy _sensorParseEnergy(const String& value) { - sensor::Energy result; - - const bool separator = value.indexOf('+') > 0; - if (value.length() && (separator > 0)) { - const String before = value.substring(0, separator); - const String after = value.substring(separator + 1); - result.kwh = strtoul(before.c_str(), nullptr, 10); - result.ws = strtoul(after.c_str(), nullptr, 10); - } - - return result; -} - -void _sensorApiResetEnergy(const sensor_magnitude_t& magnitude, const char* payload) { - if (!payload || !strlen(payload)) return; - if (payload[0] != '0') return; - - auto* sensor = static_cast(magnitude.sensor); - auto energy = _sensorParseEnergy(payload); - - sensor->resetEnergy(magnitude.global, energy); -} - -sensor::Energy _sensorEnergyTotal(unsigned char index) { - - sensor::Energy result; - - if (rtcmemStatus() && (index < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy)))) { - result = _sensorRtcmemLoadEnergy(index); - } else if (_sensor_save_every > 0) { - result = _sensorParseEnergy(getSetting({"eneTotal", index})); - } - - return result; - -} - -sensor::Energy sensorEnergyTotal() { - return _sensorEnergyTotal(0); -} - -void _sensorResetEnergyTotal(unsigned char index) { - delSetting({"eneTotal", index}); - delSetting({"eneTime", index}); - if (index < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy))) { - Rtcmem->energy[index].kwh = 0; - Rtcmem->energy[index].ws = 0; - } -} - -void _magnitudeSaveEnergyTotal(sensor_magnitude_t& magnitude, bool persistent) { - if (magnitude.type != MAGNITUDE_ENERGY) return; - - auto* sensor = static_cast(magnitude.sensor); - - const auto energy = sensor->totalEnergy(); - - // Always save to RTCMEM - if (magnitude.global < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy))) { - _sensorRtcmemSaveEnergy(magnitude.global, energy); - } - - // Save to EEPROM every '_sensor_save_every' readings - // Format is `+`, value without `+` is treated as `` - if (persistent && _sensor_save_every) { - _sensor_save_count[magnitude.global] = - (_sensor_save_count[magnitude.global] + 1) % _sensor_save_every; - - if (0 == _sensor_save_count[magnitude.global]) { - const String total = String(energy.kwh.value) + "+" + String(energy.ws.value); - setSetting({"eneTotal", magnitude.global}, total); - #if NTP_SUPPORT - if (ntpSynced()) setSetting({"eneTime", magnitude.global}, ntpDateTime()); - #endif - } - } -} - - // ----------------------------------------------------------------------------- // Sensor initialization // ----------------------------------------------------------------------------- @@ -1565,6 +1734,47 @@ void _sensorLoad() { #endif } +void _sensorReport(unsigned char index, double value) { + + const auto& magnitude = _magnitudes.at(index); + + // XXX: ensure that the received 'value' will fit here + // dtostrf 2nd arg only controls leading zeroes and the + // 3rd is only for the part after the dot + char buffer[64]; + dtostrf(value, 1, magnitude.decimals, buffer); + + #if BROKER_SUPPORT + SensorReportBroker::Publish(magnitudeTopic(magnitude.type), magnitude.global, value, buffer); + #endif + + #if MQTT_SUPPORT + + mqttSend(magnitudeTopicIndex(index).c_str(), buffer); + + #if SENSOR_PUBLISH_ADDRESSES + char topic[32]; + snprintf(topic, sizeof(topic), "%s/%s", SENSOR_ADDRESS_TOPIC, magnitudeTopic(magnitude.type).c_str()); + if (SENSOR_USE_INDEX || (sensor_magnitude_t::counts(magnitude.type) > 1)) { + mqttSend(topic, magnitude.global, magnitude.sensor->address(magnitude.local).c_str()); + } else { + mqttSend(topic, magnitude.sensor->address(magnitude.local).c_str()); + } + #endif // SENSOR_PUBLISH_ADDRESSES + + #endif // MQTT_SUPPORT + + #if THINGSPEAK_SUPPORT + tspkEnqueueMeasurement(index, buffer); + #endif // THINGSPEAK_SUPPORT + + #if DOMOTICZ_SUPPORT + domoticzSendMagnitude(magnitude.type, index, value, buffer); + #endif // DOMOTICZ_SUPPORT + +} + + void _sensorCallback(unsigned char i, unsigned char type, double value) { DEBUG_MSG_P(PSTR("[SENSOR] Sensor #%u callback, type %u, payload: '%s'\n"), i, type, String(value).c_str()); @@ -1864,46 +2074,6 @@ void _sensorConfigure() { } -void _sensorReport(unsigned char index, double value) { - - const auto& magnitude = _magnitudes.at(index); - - // XXX: ensure that the received 'value' will fit here - // dtostrf 2nd arg only controls leading zeroes and the - // 3rd is only for the part after the dot - char buffer[64]; - dtostrf(value, 1, magnitude.decimals, buffer); - - #if BROKER_SUPPORT - SensorReportBroker::Publish(magnitudeTopic(magnitude.type), magnitude.global, value, buffer); - #endif - - #if MQTT_SUPPORT - - mqttSend(magnitudeTopicIndex(index).c_str(), buffer); - - #if SENSOR_PUBLISH_ADDRESSES - char topic[32]; - snprintf(topic, sizeof(topic), "%s/%s", SENSOR_ADDRESS_TOPIC, magnitudeTopic(magnitude.type).c_str()); - if (SENSOR_USE_INDEX || (sensor_magnitude_t::counts(magnitude.type) > 1)) { - mqttSend(topic, magnitude.global, magnitude.sensor->address(magnitude.local).c_str()); - } else { - mqttSend(topic, magnitude.sensor->address(magnitude.local).c_str()); - } - #endif // SENSOR_PUBLISH_ADDRESSES - - #endif // MQTT_SUPPORT - - #if THINGSPEAK_SUPPORT - tspkEnqueueMeasurement(index, buffer); - #endif // THINGSPEAK_SUPPORT - - #if DOMOTICZ_SUPPORT - domoticzSendMagnitude(magnitude.type, index, value, buffer); - #endif // DOMOTICZ_SUPPORT - -} - // ----------------------------------------------------------------------------- // Public // ----------------------------------------------------------------------------- @@ -2139,7 +2309,7 @@ void sensorLoop() { magnitude.sensor->slot(magnitude.local).c_str(), magnitudeTopic(magnitude.type).c_str(), buffer, - magnitudeUnits(magnitude).c_str() + _magnitudeUnits(magnitude).c_str() ); } #endif // SENSOR_DEBUG diff --git a/code/espurna/sensor.h b/code/espurna/sensor.h index a59350cc..bc0276b9 100644 --- a/code/espurna/sensor.h +++ b/code/espurna/sensor.h @@ -9,7 +9,7 @@ Copyright (C) 2020 by Maxim Prokhorov #pragma once -struct sensor_magnitude_t; +#include "espurna.h" //-------------------------------------------------------------------------------- @@ -133,9 +133,6 @@ unsigned char magnitudeType(unsigned char index); // consider using index instead of type or adding stronger param type String magnitudeTopic(unsigned char type); -String magnitudeTopic(const sensor_magnitude_t& magnitude); -String magnitudeUnits(const sensor_magnitude_t& magnitude); - unsigned char sensorCount(); unsigned char magnitudeCount(); @@ -144,179 +141,8 @@ double magnitudeValue(unsigned char index); unsigned char magnitudeIndex(unsigned char index); String magnitudeTopicIndex(unsigned char index); +void sensorWebSocketMagnitudes(JsonObject& root, const String& prefix); + void sensorSetup(); void sensorLoop(); -//-------------------------------------------------------------------------------- - -#include "filters/LastFilter.h" -#include "filters/MaxFilter.h" -#include "filters/MedianFilter.h" -#include "filters/MovingAverageFilter.h" -#include "filters/SumFilter.h" - -#include "sensors/BaseSensor.h" -#include "sensors/BaseEmonSensor.h" -#include "sensors/BaseAnalogSensor.h" - -#if AM2320_SUPPORT - #include "sensors/AM2320Sensor.h" -#endif - -#if ANALOG_SUPPORT - #include "sensors/AnalogSensor.h" -#endif - -#if BH1750_SUPPORT - #include "sensors/BH1750Sensor.h" -#endif - -#if BMP180_SUPPORT - #include "sensors/BMP180Sensor.h" -#endif - -#if BMX280_SUPPORT - #include "sensors/BMX280Sensor.h" -#endif - -#if CSE7766_SUPPORT - #include "sensors/CSE7766Sensor.h" -#endif - -#if DALLAS_SUPPORT - #include "sensors/DallasSensor.h" -#endif - -#if DHT_SUPPORT - #include "sensors/DHTSensor.h" -#endif - -#if DIGITAL_SUPPORT - #include "sensors/DigitalSensor.h" -#endif - -#if ECH1560_SUPPORT - #include "sensors/ECH1560Sensor.h" -#endif - -#if EMON_ADC121_SUPPORT - #include "sensors/EmonADC121Sensor.h" -#endif - -#if EMON_ADS1X15_SUPPORT - #include "sensors/EmonADS1X15Sensor.h" -#endif - -#if EMON_ANALOG_SUPPORT - #include "sensors/EmonAnalogSensor.h" -#endif - -#if EVENTS_SUPPORT - #include "sensors/EventSensor.h" -#endif - -#if EZOPH_SUPPORT - #include "sensors/EZOPHSensor.h" -#endif - -#if GEIGER_SUPPORT - #include "sensors/GeigerSensor.h" -#endif - -#if GUVAS12SD_SUPPORT - #include "sensors/GUVAS12SDSensor.h" -#endif - -#if HLW8012_SUPPORT - #include "sensors/HLW8012Sensor.h" -#endif - -#if LDR_SUPPORT - #include "sensors/LDRSensor.h" -#endif - -#if MAX6675_SUPPORT - #include "sensors/MAX6675Sensor.h" -#endif - -#if MICS2710_SUPPORT - #include "sensors/MICS2710Sensor.h" -#endif - -#if MICS5525_SUPPORT - #include "sensors/MICS5525Sensor.h" -#endif - -#if MHZ19_SUPPORT - #include "sensors/MHZ19Sensor.h" -#endif - -#if NTC_SUPPORT - #include "sensors/NTCSensor.h" -#endif - -#if SDS011_SUPPORT - #include "sensors/SDS011Sensor.h" -#endif - -#if SENSEAIR_SUPPORT - #include "sensors/SenseAirSensor.h" -#endif - -#if PMSX003_SUPPORT - #include "sensors/PMSX003Sensor.h" -#endif - -#if PULSEMETER_SUPPORT - #include "sensors/PulseMeterSensor.h" -#endif - -#if PZEM004T_SUPPORT - #include "sensors/PZEM004TSensor.h" -#endif - -#if SHT3X_I2C_SUPPORT - #include "sensors/SHT3XI2CSensor.h" -#endif - -#if SI7021_SUPPORT - #include "sensors/SI7021Sensor.h" -#endif - -#if SONAR_SUPPORT - #include "sensors/SonarSensor.h" -#endif - -#if T6613_SUPPORT - #include "sensors/T6613Sensor.h" -#endif - -#if TMP3X_SUPPORT - #include "sensors/TMP3XSensor.h" -#endif - -#if V9261F_SUPPORT - #include "sensors/V9261FSensor.h" -#endif - -#if VEML6075_SUPPORT - #include "sensors/VEML6075Sensor.h" -#endif - -#if VL53L1X_SUPPORT - #include "sensors/VL53L1XSensor.h" -#endif - -#if ADE7953_SUPPORT - #include "sensors/ADE7953Sensor.h" -#endif - -#if SI1145_SUPPORT - #include "sensors/SI1145Sensor.h" -#endif - -#if HDC1080_SUPPORT - #include "sensors/HDC1080Sensor.h" -#endif -//-------------------------------------------------------------------------------- - diff --git a/code/espurna/sensors/I2CSensor.h b/code/espurna/sensors/I2CSensor.h index 7726c34a..3cdef6b0 100644 --- a/code/espurna/sensors/I2CSensor.h +++ b/code/espurna/sensors/I2CSensor.h @@ -26,6 +26,7 @@ #pragma once #include "BaseSensor.h" +#include "../i2c.h" // TODO: Must inherit from I2CSensor<>, not just I2CSensor. Even with default value :( // Perhaps I2CSensor should be alias for I2CSensorBase? diff --git a/code/espurna/settings.ino b/code/espurna/settings.cpp similarity index 66% rename from code/espurna/settings.ino rename to code/espurna/settings.cpp index dea63fd8..75f328a6 100644 --- a/code/espurna/settings.ino +++ b/code/espurna/settings.cpp @@ -6,17 +6,15 @@ Copyright (C) 2016-2019 by Xose Pérez */ -#include - -#include - -#include "storage_eeprom.h" - -#include "settings_internal.h" #include "settings.h" +#include +#include + +#include + // ----------------------------------------------------------------------------- -// Reverse engineering EEPROM storage format +// (HACK) Embedis storage format, reverse engineered // ----------------------------------------------------------------------------- unsigned long settingsSize() { @@ -28,9 +26,94 @@ unsigned long settingsSize() { return SPI_FLASH_SEC_SIZE - pos + EEPROM_DATA_END; } +// -------------------------------------------------------------------------- + +namespace settings { +namespace internal { + +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; + +} + +// -------------------------------------------------------------------------- + +template <> +float convert(const String& value) { + return atof(value.c_str()); +} + +template <> +double convert(const String& value) { + return atof(value.c_str()); +} + +template <> +int convert(const String& value) { + return value.toInt(); +} + +template <> +long convert(const String& value) { + return value.toInt(); +} + +template <> +bool convert(const String& value) { + return convert(value) == 1; +} + +template <> +unsigned long convert(const String& value) { + if (!value.length()) { + return 0; + } + + int base = 10; + if (value.length() > 2) { + if (value.startsWith("0b")) { + base = 2; + } else if (value.startsWith("0o")) { + base = 8; + } else if (value.startsWith("0x")) { + base = 16; + } + } + + return u32fromString((base == 10) ? value : value.substring(2), base); +} + +template <> +unsigned int convert(const String& value) { + return convert(value); +} + +template <> +unsigned short convert(const String& value) { + return convert(value); +} + +template <> +unsigned char convert(const String& value) { + return convert(value); +} + +} // namespace settings::internal +} // namespace settings + // ----------------------------------------------------------------------------- -unsigned int settingsKeyCount() { +size_t settingsKeyCount() { unsigned count = 0; unsigned pos = SPI_FLASH_SEC_SIZE - 1; while (size_t len = EEPROMr.read(pos)) { @@ -68,13 +151,72 @@ String settingsKeyName(unsigned int index) { } -std::vector _settingsKeys() { +/* +struct SettingsKeys { + + struct iterator { + iterator(size_t total) : + total(total) + {} + + iterator& operator++() { + if (total && (current_index < (total - 1))) { + ++current_index + current_value = settingsKeyName(current_index); + return *this; + } + return end(); + } + + iterator operator++(int) { + iterator val = *this; + ++(*this); + return val; + } + + operator String() { + return (current_index < total) ? current_value : empty_value; + } + + bool operator ==(iterator& const other) const { + return (total == other.total) && (current_index == other.current_index); + } + + bool operator !=(iterator& const other) const { + return !(*this == other); + } + + using difference_type = size_t; + using value_type = size_t; + using pointer = const size_t*; + using reference = const size_t&; + using iterator_category = std::forward_iterator_tag; + + const size_t total; + + String empty_value; + String current_value; + size_t current_index = 0; + }; + + iterator begin() { + return iterator {total}; + } + + iterator end() { + return iterator {0}; + } + +}; +*/ + +std::vector settingsKeys() { // Get sorted list of keys std::vector keys; //unsigned int size = settingsKeyCount(); - unsigned int size = settingsKeyCount(); + auto size = settingsKeyCount(); for (unsigned int i=0; i Rfunc = settings::internal::convert> R getSetting(const settings_key_t& key, R defaultValue) { String value; @@ -149,6 +292,7 @@ R getSetting(const settings_key_t& key, R defaultValue) { } return Rfunc(value); } +#endif template<> String getSetting(const settings_key_t& key, String defaultValue) { @@ -159,6 +303,33 @@ String getSetting(const settings_key_t& key, String defaultValue) { return value; } +template +bool getSetting(const settings_key_t& key, bool defaultValue); + +template +int getSetting(const settings_key_t& key, int defaultValue); + +template +long getSetting(const settings_key_t& key, long defaultValue); + +template +unsigned char getSetting(const settings_key_t& key, unsigned char defaultValue); + +template +unsigned short getSetting(const settings_key_t& key, unsigned short defaultValue); + +template +unsigned int getSetting(const settings_key_t& key, unsigned int defaultValue); + +template +unsigned long getSetting(const settings_key_t& key, unsigned long defaultValue); + +template +float getSetting(const settings_key_t& key, float defaultValue); + +template +double getSetting(const settings_key_t& key, double defaultValue); + String getSetting(const settings_key_t& key) { static const String defaultValue(""); return getSetting(key, defaultValue); @@ -172,9 +343,9 @@ String getSetting(const settings_key_t& key, const __FlashStringHelper* defaultV return getSetting(key, String(defaultValue)); } -template -bool setSetting(const settings_key_t& key, const T& value) { - return Embedis::set(key.toString(), String(value)); +template<> +bool setSetting(const settings_key_t& key, const String& value) { + return Embedis::set(key.toString(), value); } bool delSetting(const settings_key_t& key) { @@ -199,30 +370,6 @@ void resetSettings() { EEPROMr.commit(); } -// ----------------------------------------------------------------------------- -// Deprecated implementation -// ----------------------------------------------------------------------------- - -template -String getSetting(const String& key, unsigned char index, T defaultValue) { - return getSetting({key, index}, defaultValue); -} - -template -bool setSetting(const String& key, unsigned char index, T value) { - return setSetting({key, index}, value); -} - -template -bool hasSetting(const String& key, unsigned char index) { - return hasSetting({key, index}); -} - -template -bool delSetting(const String& key, unsigned char index) { - return delSetting({key, index}); -} - // ----------------------------------------------------------------------------- // API // ----------------------------------------------------------------------------- @@ -267,7 +414,7 @@ bool settingsRestoreJson(JsonObject& data) { } -bool settingsRestoreJson(char* json_string, size_t json_buffer_size = 1024) { +bool settingsRestoreJson(char* json_string, size_t json_buffer_size) { // XXX: as of right now, arduinojson cannot trigger callbacks for each key individually // Manually separating kv pairs can allow to parse only a small chunk, since we know that there is only string type used (even with bools / ints). Can be problematic when parsing data that was not generated by us. @@ -287,7 +434,7 @@ bool settingsRestoreJson(char* json_string, size_t json_buffer_size = 1024) { void settingsGetJson(JsonObject& root) { // Get sorted list of keys - std::vector keys = _settingsKeys(); + auto keys = settingsKeys(); // Add the key-values to the json object for (unsigned int i=0; i #pragma once +#include + #include #include #include #include +#include "espurna.h" #include "libs/EmbedisWrap.h" -#include "settings_internal.h" // -------------------------------------------------------------------------- @@ -63,17 +65,83 @@ using settings_cfg_list_t = std::initializer_list; // -------------------------------------------------------------------------- +namespace settings { +namespace internal { + +uint32_t u32fromString(const String& string, int base); + +template +using convert_t = T(*)(const String& value); + +template +T convert(const String& value); + +// -------------------------------------------------------------------------- + +template <> +float convert(const String& value); + +template <> +double convert(const String& value); + +template <> +int convert(const String& value); + +template <> +long convert(const String& value); + +template <> +bool convert(const String& value); + +template <> +unsigned long convert(const String& value); + +template <> +unsigned int convert(const String& value); + +template <> +unsigned short convert(const String& value); + +template <> +unsigned char convert(const String& value); + +} // namespace settings::internal +} // namespace settings + +// -------------------------------------------------------------------------- + void moveSetting(const String& from, const String& to); void moveSetting(const String& from, const String& to, unsigned int index); void moveSettings(const String& from, const String& to); +#if 1 template Rfunc = settings::internal::convert> -R getSetting(const settings_key_t& key, R defaultValue); +R getSetting(const settings_key_t& key, R defaultValue) __attribute__((noinline)); +#endif + +template Rfunc = settings::internal::convert> +R getSetting(const settings_key_t& key, R defaultValue) { + String value; + if (!Embedis::get(key.toString(), value)) { + return defaultValue; + } + return Rfunc(value); +} + +template<> +String getSetting(const settings_key_t& key, String defaultValue); String getSetting(const settings_key_t& key); +String getSetting(const settings_key_t& key, const char* defaultValue); +String getSetting(const settings_key_t& key, const __FlashStringHelper* defaultValue); template -bool setSetting(const settings_key_t& key, const T& value); +bool setSetting(const settings_key_t& key, const T& value) { + return Embedis::set(key.toString(), String(value)); +} + +template<> +bool setSetting(const settings_key_t& key, const String& value); bool delSetting(const settings_key_t& key); bool hasSetting(const settings_key_t& key); @@ -82,11 +150,23 @@ void saveSettings(); void resetSettings(); void settingsGetJson(JsonObject& data); +bool settingsRestoreJson(char* json_string, size_t json_buffer_size = 1024); bool settingsRestoreJson(JsonObject& data); +size_t settingsKeyCount(); +String settingsKeyName(unsigned int index); +std::vector settingsKeys(); + void settingsProcessConfig(const settings_cfg_list_t& config, settings_filter_t filter = nullptr); -// -------------------------------------------------------------------------- +unsigned long settingsSize(); + +void migrate(); +void settingsSetup(); + +// ----------------------------------------------------------------------------- +// Deprecated implementation +// ----------------------------------------------------------------------------- template String getSetting(const String& key, unsigned char index, T defaultValue) @@ -103,3 +183,26 @@ __attribute__((deprecated("hasSetting({key, index}) should be used instead"))); template bool delSetting(const String& key, unsigned char index) __attribute__((deprecated("delSetting({key, index}) should be used instead"))); + +// -------------------------------------------------------------------------- + +template +String getSetting(const String& key, unsigned char index, T defaultValue) { + return getSetting({key, index}, defaultValue); +} + +template +bool setSetting(const String& key, unsigned char index, T value) { + return setSetting({key, index}, value); +} + +template +bool hasSetting(const String& key, unsigned char index) { + return hasSetting({key, index}); +} + +template +bool delSetting(const String& key, unsigned char index) { + return delSetting({key, index}); +} + diff --git a/code/espurna/settings_internal.h b/code/espurna/settings_internal.h index 65f3dab7..e5de7b9a 100644 --- a/code/espurna/settings_internal.h +++ b/code/espurna/settings_internal.h @@ -6,70 +6,6 @@ SETTINGS MODULE #pragma once +#include #include -// -------------------------------------------------------------------------- - -namespace settings { -namespace internal { - -template -using convert_t = T(*)(const String& value); - -template -T convert(const String& value); - -// -------------------------------------------------------------------------- - -template <> -float convert(const String& value) { - return value.toFloat(); -} - -template <> -double convert(const String& value) { - return value.toFloat(); -} - -template <> -int convert(const String& value) { - return value.toInt(); -} - -template <> -long convert(const String& value) { - return value.toInt(); -} - -template <> -bool convert(const String& value) { - return convert(value) == 1; -} - -template <> -unsigned long convert(const String& value) { - return strtoul(value.c_str(), nullptr, 10); -} - -template <> -unsigned int convert(const String& value) { - return convert(value); -} - -template <> -unsigned short convert(const String& value) { - return convert(value); -} - -template <> -unsigned char convert(const String& value) { - return convert(value); -} - -template <> -String convert(const String& value) { - return value; -} - -} // namespace settings::internal -} // namespace settings diff --git a/code/espurna/ssdp.ino b/code/espurna/ssdp.cpp similarity index 98% rename from code/espurna/ssdp.ino rename to code/espurna/ssdp.cpp index bd65e61e..41cd39c9 100644 --- a/code/espurna/ssdp.ino +++ b/code/espurna/ssdp.cpp @@ -8,10 +8,13 @@ https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604 */ +#include "ssdp.h" + #if SSDP_SUPPORT #include +#include "web.h" #include "utils.h" const char _ssdp_template[] PROGMEM = diff --git a/code/espurna/ssdp.h b/code/espurna/ssdp.h new file mode 100644 index 00000000..e38c4c41 --- /dev/null +++ b/code/espurna/ssdp.h @@ -0,0 +1,20 @@ +/* + +SSDP MODULE + +Copyright (C) 2017-2019 by Xose Pérez +Uses SSDP library by PawelDino (https://github.com/PawelDino) +https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604 + +*/ + +#include "espurna.h" + +#if SSDP_SUPPORT + +#include + +void ssdpSetup(); + +#endif // SSDP_SUPPORT + diff --git a/code/espurna/storage_eeprom.ino b/code/espurna/storage_eeprom.cpp similarity index 99% rename from code/espurna/storage_eeprom.ino rename to code/espurna/storage_eeprom.cpp index e4b65af8..805660ca 100644 --- a/code/espurna/storage_eeprom.ino +++ b/code/espurna/storage_eeprom.cpp @@ -4,8 +4,8 @@ EEPROM MODULE */ -#include "debug.h" #include "storage_eeprom.h" +#include "settings.h" EEPROM_Rotate EEPROMr; bool _eeprom_commit = false; diff --git a/code/espurna/storage_eeprom.h b/code/espurna/storage_eeprom.h index a12958c4..75cc0f3c 100644 --- a/code/espurna/storage_eeprom.h +++ b/code/espurna/storage_eeprom.h @@ -11,6 +11,8 @@ EEPROM MODULE #include +#include "espurna.h" + extern EEPROM_Rotate EEPROMr; void eepromSectorsDebug(); diff --git a/code/espurna/system.ino b/code/espurna/system.cpp similarity index 98% rename from code/espurna/system.ino rename to code/espurna/system.cpp index d4a61655..b5a5a9d1 100644 --- a/code/espurna/system.ino +++ b/code/espurna/system.cpp @@ -6,11 +6,17 @@ Copyright (C) 2019 by Xose Pérez */ -#include -#include - #include "system.h" +#include +#include + +#include + +#include "rtcmem.h" +#include "ws.h" +#include "ntp.h" + // ----------------------------------------------------------------------------- bool _system_send_heartbeat = false; diff --git a/code/espurna/system.h b/code/espurna/system.h index daf66cec..fe2649bf 100644 --- a/code/espurna/system.h +++ b/code/espurna/system.h @@ -8,10 +8,7 @@ Copyright (C) 2019 by Xose Pérez #pragma once -#include -#include - -#include +#include "espurna.h" extern "C" { #include "user_interface.h" @@ -31,3 +28,8 @@ void customResetReason(unsigned char reason); void deferredReset(unsigned long delay, unsigned char reason); bool checkNeedsReset(); + +unsigned long systemLoadAverage(); +bool systemGetHeartbeat(); +void systemSendHeartbeat(); +void systemSetup(); diff --git a/code/espurna/telnet.ino b/code/espurna/telnet.cpp similarity index 99% rename from code/espurna/telnet.ino rename to code/espurna/telnet.cpp index 1c093288..7d7e6d72 100644 --- a/code/espurna/telnet.ino +++ b/code/espurna/telnet.cpp @@ -15,11 +15,14 @@ Updated to use WiFiServer and support reverse connections by Niek van der Maas < */ +#include "telnet.h" + #if TELNET_SUPPORT #include + #include "board.h" -#include "telnet.h" +#include "ws.h" TTelnetServer _telnetServer(TELNET_PORT); std::unique_ptr _telnetClients[TELNET_MAX_CLIENTS]; diff --git a/code/espurna/telnet.h b/code/espurna/telnet.h index f791461e..a9c13182 100644 --- a/code/espurna/telnet.h +++ b/code/espurna/telnet.h @@ -8,12 +8,16 @@ Copyright (C) 2017-2019 by Xose Pérez #pragma once +#include "espurna.h" + +#include +#include + #include #include -#include +#if TELNET_SERVER == TELNET_SERVER_ASYNC -#include #include struct AsyncBufferedClient { @@ -44,18 +48,28 @@ struct AsyncBufferedClient { std::list _buffers; }; -#if TELNET_SERVER == TELNET_SERVER_WIFISERVER - using TTelnetServer = WiFiServer; - using TTelnetClient = WiFiClient; -#elif TELNET_SERVER == TELNET_SERVER_ASYNC - using TTelnetServer = AsyncServer; +using TTelnetServer = AsyncServer; + #if TELNET_SERVER_ASYNC_BUFFERED using TTelnetClient = AsyncBufferedClient; #else using TTelnetClient = AsyncClient; #endif // TELNET_SERVER_ASYNC_BUFFERED + +#elif TELNET_SERVER == TELNET_SERVER_WIFISERVER + +using TTelnetServer = WiFiServer; +using TTelnetClient = WiFiClient; + +#else +#error "TELNET_SERVER value was not properly set" #endif -constexpr const char TELNET_IAC = 0xFF; -constexpr const char TELNET_XEOF = 0xEC; +constexpr unsigned char TELNET_IAC = 0xFF; +constexpr unsigned char TELNET_XEOF = 0xEC; + +bool telnetConnected(); +unsigned char telnetWrite(unsigned char ch); +bool telnetDebugSend(const char* prefix, const char* data); +void telnetSetup(); diff --git a/code/espurna/terminal.ino b/code/espurna/terminal.cpp similarity index 98% rename from code/espurna/terminal.ino rename to code/espurna/terminal.cpp index d5f0751e..8c4516a9 100644 --- a/code/espurna/terminal.ino +++ b/code/espurna/terminal.cpp @@ -6,14 +6,20 @@ Copyright (C) 2016-2019 by Xose Pérez */ +// (HACK) allow us to use internal lwip struct. +// esp8266 re-defines enum values from tcp header... include them first +#include "terminal.h" + #if TERMINAL_SUPPORT #include "settings.h" #include "system.h" -#include "terminal.h" +#include "telnet.h" #include "utils.h" +#include "wifi.h" +#include "ws.h" +#include "libs/URL.h" #include "libs/StreamInjector.h" -#include "libs/HeapStats.h" #include #include @@ -66,7 +72,7 @@ void _terminalHelpCommand() { void _terminalKeysCommand() { // Get sorted list of keys - std::vector keys = _settingsKeys(); + auto keys = settingsKeys(); // Write key-values DEBUG_MSG_P(PSTR("Current settings:\n")); diff --git a/code/espurna/terminal.h b/code/espurna/terminal.h index 4bd6cdf3..6dbb121b 100644 --- a/code/espurna/terminal.h +++ b/code/espurna/terminal.h @@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #if TERMINAL_SUPPORT #include "libs/EmbedisWrap.h" @@ -19,6 +21,7 @@ void terminalError(const String& error); void terminalRegisterCommand(const String& name, embedis_command_f func); void terminalInject(void *data, size_t len); +void terminalInject(char ch); Stream& terminalSerial(); void terminalSetup(); diff --git a/code/espurna/thermostat.ino b/code/espurna/thermostat.cpp similarity index 94% rename from code/espurna/thermostat.ino rename to code/espurna/thermostat.cpp index 1adf88ac..2b8295ff 100644 --- a/code/espurna/thermostat.ino +++ b/code/espurna/thermostat.cpp @@ -6,19 +6,16 @@ Copyright (C) 2017 by Dmitry Blinov */ -#if THERMOSTAT_SUPPORT +#include "thermostat.h" -#include +#if THERMOSTAT_SUPPORT #include "ntp.h" #include "relay.h" -#include "thermostat.h" +#include "sensor.h" +#include "mqtt.h" #include "ws.h" - -bool _thermostat_enabled = true; -bool _thermostat_mode_cooler = false; - const char* NAME_THERMOSTAT_ENABLED = "thermostatEnabled"; const char* NAME_THERMOSTAT_MODE = "thermostatMode"; const char* NAME_TEMP_RANGE_MIN = "tempRangeMin"; @@ -38,25 +35,6 @@ const char* NAME_BURN_DAY = "burnDay"; const char* NAME_BURN_MONTH = "burnMonth"; const char* NAME_OPERATION_MODE = "thermostatOperationMode"; -#define ASK_TEMP_RANGE_INTERVAL_INITIAL 15000 // ask initially once per every 15 seconds -#define ASK_TEMP_RANGE_INTERVAL_REGULAR 60000 // ask every minute to be sure -#define MILLIS_IN_SEC 1000 -#define MILLIS_IN_MIN 60000 -#define THERMOSTAT_STATE_UPDATE_INTERVAL 60000 // 1 min -#define THERMOSTAT_RELAY 0 // use relay 0 -#define THERMOSTAT_TEMP_RANGE_MIN 10 // grad. Celsius -#define THERMOSTAT_TEMP_RANGE_MIN_MIN 3 // grad. Celsius -#define THERMOSTAT_TEMP_RANGE_MIN_MAX 30 // grad. Celsius -#define THERMOSTAT_TEMP_RANGE_MAX 20 // grad. Celsius -#define THERMOSTAT_TEMP_RANGE_MAX_MIN 8 // grad. Celsius -#define THERMOSTAT_TEMP_RANGE_MAX_MAX 35 // grad. Celsius -#define THERMOSTAT_ALONE_ON_TIME 5 // 5 min -#define THERMOSTAT_ALONE_OFF_TIME 55 // 55 min -#define THERMOSTAT_MAX_ON_TIME 30 // 30 min -#define THERMOSTAT_MIN_OFF_TIME 10 // 10 min -#define THERMOSTAT_ENABLED_BY_DEFAULT true -#define THERMOSTAT_MODE_COOLER_BY_DEFAULT false - unsigned long _thermostat_remote_temp_max_wait = THERMOSTAT_REMOTE_TEMP_MAX_WAIT * MILLIS_IN_SEC; unsigned long _thermostat_alone_on_time = THERMOSTAT_ALONE_ON_TIME * MILLIS_IN_MIN; unsigned long _thermostat_alone_off_time = THERMOSTAT_ALONE_OFF_TIME * MILLIS_IN_MIN; @@ -71,23 +49,6 @@ unsigned int _thermostat_burn_prev_month = 0; unsigned int _thermostat_burn_day = 0; unsigned int _thermostat_burn_month = 0; -struct temp_t { - float temp; - unsigned long last_update = 0; - bool need_display_update = false; -}; -temp_t _remote_temp; - -struct temp_range_t { - int min = THERMOSTAT_TEMP_RANGE_MIN; - int max = THERMOSTAT_TEMP_RANGE_MAX; - unsigned long last_update = 0; - unsigned long ask_time = 0; - unsigned long ask_interval = ASK_TEMP_RANGE_INTERVAL_INITIAL; - bool need_display_update = true; -}; -temp_range_t _temp_range; - enum temperature_source_t {temp_none, temp_local, temp_remote}; struct thermostat_t { unsigned long last_update = 0; @@ -95,12 +56,28 @@ struct thermostat_t { String remote_sensor_name; unsigned int temperature_source = temp_none; }; + +bool _thermostat_enabled = true; +bool _thermostat_mode_cooler = false; + +temp_t _remote_temp; +temp_range_t _temp_range; thermostat_t _thermostat; enum thermostat_cycle_type {cooling, heating}; unsigned int _thermostat_cycle = heating; String thermostat_remote_sensor_topic; +//------------------------------------------------------------------------------ +const temp_t& thermostatRemoteTemp() { + return _remote_temp; +} + +//------------------------------------------------------------------------------ +const temp_range_t& thermostatRange() { + return _temp_range; +} + //------------------------------------------------------------------------------ void thermostatEnabled(bool enabled) { _thermostat_enabled = enabled; @@ -128,24 +105,6 @@ void thermostatRegister(thermostat_callback_f callback) { _thermostat_callbacks.push_back(callback); } -//------------------------------------------------------------------------------ -void updateOperationMode() { - #if WEB_SUPPORT - String message; - if (_thermostat.temperature_source == temp_remote) { - message = "{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"remote temperature\"}"; - updateRemoteTemp(true); - } else if (_thermostat.temperature_source == temp_local) { - message = "{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"local temperature\"}"; - updateRemoteTemp(false); - } else { - message = "{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"autonomous\"}"; - updateRemoteTemp(false); - } - wsSend(message.c_str()); - #endif -} - //------------------------------------------------------------------------------ void updateRemoteTemp(bool remote_temp_actual) { #if WEB_SUPPORT @@ -161,6 +120,25 @@ void updateRemoteTemp(bool remote_temp_actual) { #endif } +//------------------------------------------------------------------------------ +void updateOperationMode() { + #if WEB_SUPPORT + String message(F("{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"")); + if (_thermostat.temperature_source == temp_remote) { + message += F("remote temperature"); + updateRemoteTemp(true); + } else if (_thermostat.temperature_source == temp_local) { + message += F("local temperature"); + updateRemoteTemp(false); + } else { + message += F("autonomous"); + updateRemoteTemp(false); + } + message += F("\"}"); + wsSend(message.c_str()); + #endif +} + //------------------------------------------------------------------------------ // MQTT //------------------------------------------------------------------------------ @@ -233,13 +211,6 @@ void thermostatMQTTCallback(unsigned int type, const char * topic, const char * } } -#if MQTT_SUPPORT -//------------------------------------------------------------------------------ -void thermostatSetupMQTT() { - mqttRegister(thermostatMQTTCallback); -} -#endif - //------------------------------------------------------------------------------ void notifyRangeChanged(bool min) { DEBUG_MSG_P(PSTR("[THERMOSTAT] notifyRangeChanged %s = %d\n"), min ? "MIN" : "MAX", min ? _temp_range.min : _temp_range.max); @@ -274,35 +245,6 @@ void commonSetup() { _thermostat_min_off_time = getSetting(NAME_MIN_OFF_TIME, THERMOSTAT_MIN_OFF_TIME) * MILLIS_IN_MIN; } -//------------------------------------------------------------------------------ -void thermostatSetup() { - commonSetup(); - - _thermostat.temperature_source = temp_none; - _thermostat_burn_total = getSetting(NAME_BURN_TOTAL, 0); - _thermostat_burn_today = getSetting(NAME_BURN_TODAY, 0); - _thermostat_burn_yesterday = getSetting(NAME_BURN_YESTERDAY, 0); - _thermostat_burn_this_month = getSetting(NAME_BURN_THIS_MONTH, 0); - _thermostat_burn_prev_month = getSetting(NAME_BURN_PREV_MONTH, 0); - _thermostat_burn_day = getSetting(NAME_BURN_DAY, 0); - _thermostat_burn_month = getSetting(NAME_BURN_MONTH, 0); - - #if MQTT_SUPPORT - thermostatSetupMQTT(); - #endif - - // Websockets - #if WEB_SUPPORT - wsRegister() - .onConnected(_thermostatWebSocketOnConnected) - .onKeyCheck(_thermostatWebSocketOnKeyCheck) - .onAction(_thermostatWebSocketOnAction); - #endif - - espurnaRegisterLoop(thermostatLoop); - espurnaRegisterReload(_thermostatReload); -} - //------------------------------------------------------------------------------ void _thermostatReload() { int prev_temp_range_min = _temp_range.min; @@ -316,58 +258,6 @@ void _thermostatReload() { notifyRangeChanged(false); } -#if WEB_SUPPORT -//------------------------------------------------------------------------------ -void _thermostatWebSocketOnConnected(JsonObject& root) { - root["thermostatEnabled"] = thermostatEnabled(); - root["thermostatMode"] = thermostatModeCooler(); - root["thermostatVisible"] = 1; - root[NAME_TEMP_RANGE_MIN] = _temp_range.min; - root[NAME_TEMP_RANGE_MAX] = _temp_range.max; - root[NAME_REMOTE_SENSOR_NAME] = _thermostat.remote_sensor_name; - root[NAME_REMOTE_TEMP_MAX_WAIT] = _thermostat_remote_temp_max_wait / MILLIS_IN_SEC; - root[NAME_MAX_ON_TIME] = _thermostat_max_on_time / MILLIS_IN_MIN; - root[NAME_MIN_OFF_TIME] = _thermostat_min_off_time / MILLIS_IN_MIN; - root[NAME_ALONE_ON_TIME] = _thermostat_alone_on_time / MILLIS_IN_MIN; - root[NAME_ALONE_OFF_TIME] = _thermostat_alone_off_time / MILLIS_IN_MIN; - root[NAME_BURN_TODAY] = _thermostat_burn_today; - root[NAME_BURN_YESTERDAY] = _thermostat_burn_yesterday; - root[NAME_BURN_THIS_MONTH] = _thermostat_burn_this_month; - root[NAME_BURN_PREV_MONTH] = _thermostat_burn_prev_month; - root[NAME_BURN_TOTAL] = _thermostat_burn_total; - if (_thermostat.temperature_source == temp_remote) { - root[NAME_OPERATION_MODE] = "remote temperature"; - root["remoteTmp"] = _remote_temp.temp; - } else if (_thermostat.temperature_source == temp_local) { - root[NAME_OPERATION_MODE] = "local temperature"; - root["remoteTmp"] = "?"; - } else { - root[NAME_OPERATION_MODE] = "autonomous"; - root["remoteTmp"] = "?"; - } -} - -//------------------------------------------------------------------------------ -bool _thermostatWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - if (strncmp(key, NAME_THERMOSTAT_ENABLED, strlen(NAME_THERMOSTAT_ENABLED)) == 0) return true; - if (strncmp(key, NAME_THERMOSTAT_MODE, strlen(NAME_THERMOSTAT_MODE)) == 0) return true; - if (strncmp(key, NAME_TEMP_RANGE_MIN, strlen(NAME_TEMP_RANGE_MIN)) == 0) return true; - if (strncmp(key, NAME_TEMP_RANGE_MAX, strlen(NAME_TEMP_RANGE_MAX)) == 0) return true; - if (strncmp(key, NAME_REMOTE_SENSOR_NAME, strlen(NAME_REMOTE_SENSOR_NAME)) == 0) return true; - if (strncmp(key, NAME_REMOTE_TEMP_MAX_WAIT, strlen(NAME_REMOTE_TEMP_MAX_WAIT)) == 0) return true; - if (strncmp(key, NAME_MAX_ON_TIME, strlen(NAME_MAX_ON_TIME)) == 0) return true; - if (strncmp(key, NAME_MIN_OFF_TIME, strlen(NAME_MIN_OFF_TIME)) == 0) return true; - if (strncmp(key, NAME_ALONE_ON_TIME, strlen(NAME_ALONE_ON_TIME)) == 0) return true; - if (strncmp(key, NAME_ALONE_OFF_TIME, strlen(NAME_ALONE_OFF_TIME)) == 0) return true; - return false; -} - -//------------------------------------------------------------------------------ -void _thermostatWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { - if (strcmp(action, "thermostat_reset_counters") == 0) resetBurnCounters(); -} -#endif - //------------------------------------------------------------------------------ void sendTempRangeRequest() { DEBUG_MSG_P(PSTR("[THERMOSTAT] sendTempRangeRequest\n")); @@ -586,8 +476,6 @@ void resetBurnCounters() { _thermostat_burn_prev_month = 0; } -#endif // THERMOSTAT_SUPPORT - //####################################################################### // ___ _ _ // | \ (_) ___ _ __ | | __ _ _ _ @@ -849,3 +737,86 @@ void displayLoop() { } #endif // THERMOSTAT_DISPLAY_SUPPORT + +#if WEB_SUPPORT +//------------------------------------------------------------------------------ +void _thermostatWebSocketOnConnected(JsonObject& root) { + root["thermostatEnabled"] = thermostatEnabled(); + root["thermostatMode"] = thermostatModeCooler(); + root["thermostatVisible"] = 1; + root[NAME_TEMP_RANGE_MIN] = _temp_range.min; + root[NAME_TEMP_RANGE_MAX] = _temp_range.max; + root[NAME_REMOTE_SENSOR_NAME] = _thermostat.remote_sensor_name; + root[NAME_REMOTE_TEMP_MAX_WAIT] = _thermostat_remote_temp_max_wait / MILLIS_IN_SEC; + root[NAME_MAX_ON_TIME] = _thermostat_max_on_time / MILLIS_IN_MIN; + root[NAME_MIN_OFF_TIME] = _thermostat_min_off_time / MILLIS_IN_MIN; + root[NAME_ALONE_ON_TIME] = _thermostat_alone_on_time / MILLIS_IN_MIN; + root[NAME_ALONE_OFF_TIME] = _thermostat_alone_off_time / MILLIS_IN_MIN; + root[NAME_BURN_TODAY] = _thermostat_burn_today; + root[NAME_BURN_YESTERDAY] = _thermostat_burn_yesterday; + root[NAME_BURN_THIS_MONTH] = _thermostat_burn_this_month; + root[NAME_BURN_PREV_MONTH] = _thermostat_burn_prev_month; + root[NAME_BURN_TOTAL] = _thermostat_burn_total; + if (_thermostat.temperature_source == temp_remote) { + root[NAME_OPERATION_MODE] = "remote temperature"; + root["remoteTmp"] = _remote_temp.temp; + } else if (_thermostat.temperature_source == temp_local) { + root[NAME_OPERATION_MODE] = "local temperature"; + root["remoteTmp"] = "?"; + } else { + root[NAME_OPERATION_MODE] = "autonomous"; + root["remoteTmp"] = "?"; + } +} + +//------------------------------------------------------------------------------ +bool _thermostatWebSocketOnKeyCheck(const char * key, JsonVariant& value) { + if (strncmp(key, NAME_THERMOSTAT_ENABLED, strlen(NAME_THERMOSTAT_ENABLED)) == 0) return true; + if (strncmp(key, NAME_THERMOSTAT_MODE, strlen(NAME_THERMOSTAT_MODE)) == 0) return true; + if (strncmp(key, NAME_TEMP_RANGE_MIN, strlen(NAME_TEMP_RANGE_MIN)) == 0) return true; + if (strncmp(key, NAME_TEMP_RANGE_MAX, strlen(NAME_TEMP_RANGE_MAX)) == 0) return true; + if (strncmp(key, NAME_REMOTE_SENSOR_NAME, strlen(NAME_REMOTE_SENSOR_NAME)) == 0) return true; + if (strncmp(key, NAME_REMOTE_TEMP_MAX_WAIT, strlen(NAME_REMOTE_TEMP_MAX_WAIT)) == 0) return true; + if (strncmp(key, NAME_MAX_ON_TIME, strlen(NAME_MAX_ON_TIME)) == 0) return true; + if (strncmp(key, NAME_MIN_OFF_TIME, strlen(NAME_MIN_OFF_TIME)) == 0) return true; + if (strncmp(key, NAME_ALONE_ON_TIME, strlen(NAME_ALONE_ON_TIME)) == 0) return true; + if (strncmp(key, NAME_ALONE_OFF_TIME, strlen(NAME_ALONE_OFF_TIME)) == 0) return true; + return false; +} + +//------------------------------------------------------------------------------ +void _thermostatWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { + if (strcmp(action, "thermostat_reset_counters") == 0) resetBurnCounters(); +} +#endif + +//------------------------------------------------------------------------------ +void thermostatSetup() { + commonSetup(); + + _thermostat.temperature_source = temp_none; + _thermostat_burn_total = getSetting(NAME_BURN_TOTAL, 0); + _thermostat_burn_today = getSetting(NAME_BURN_TODAY, 0); + _thermostat_burn_yesterday = getSetting(NAME_BURN_YESTERDAY, 0); + _thermostat_burn_this_month = getSetting(NAME_BURN_THIS_MONTH, 0); + _thermostat_burn_prev_month = getSetting(NAME_BURN_PREV_MONTH, 0); + _thermostat_burn_day = getSetting(NAME_BURN_DAY, 0); + _thermostat_burn_month = getSetting(NAME_BURN_MONTH, 0); + + #if MQTT_SUPPORT + mqttRegister(thermostatMQTTCallback); + #endif + + // Websockets + #if WEB_SUPPORT + wsRegister() + .onConnected(_thermostatWebSocketOnConnected) + .onKeyCheck(_thermostatWebSocketOnKeyCheck) + .onAction(_thermostatWebSocketOnAction); + #endif + + espurnaRegisterLoop(thermostatLoop); + espurnaRegisterReload(_thermostatReload); +} + +#endif // THERMOSTAT_SUPPORT diff --git a/code/espurna/thermostat.h b/code/espurna/thermostat.h index a25c7a90..82bc2765 100644 --- a/code/espurna/thermostat.h +++ b/code/espurna/thermostat.h @@ -8,6 +8,8 @@ Copyright (C) 2017 by Dmitry Blinov #pragma once +#include "espurna.h" + #include #include @@ -15,5 +17,50 @@ Copyright (C) 2017 by Dmitry Blinov #include // alias for `#include "SSD1306Wire.h"` #endif +#define ASK_TEMP_RANGE_INTERVAL_INITIAL 15000 // ask initially once per every 15 seconds +#define ASK_TEMP_RANGE_INTERVAL_REGULAR 60000 // ask every minute to be sure +#define MILLIS_IN_SEC 1000 +#define MILLIS_IN_MIN 60000 +#define THERMOSTAT_STATE_UPDATE_INTERVAL 60000 // 1 min +#define THERMOSTAT_RELAY 0 // use relay 0 +#define THERMOSTAT_TEMP_RANGE_MIN 10 // grad. Celsius +#define THERMOSTAT_TEMP_RANGE_MIN_MIN 3 // grad. Celsius +#define THERMOSTAT_TEMP_RANGE_MIN_MAX 30 // grad. Celsius +#define THERMOSTAT_TEMP_RANGE_MAX 20 // grad. Celsius +#define THERMOSTAT_TEMP_RANGE_MAX_MIN 8 // grad. Celsius +#define THERMOSTAT_TEMP_RANGE_MAX_MAX 35 // grad. Celsius +#define THERMOSTAT_ALONE_ON_TIME 5 // 5 min +#define THERMOSTAT_ALONE_OFF_TIME 55 // 55 min +#define THERMOSTAT_MAX_ON_TIME 30 // 30 min +#define THERMOSTAT_MIN_OFF_TIME 10 // 10 min +#define THERMOSTAT_ENABLED_BY_DEFAULT true +#define THERMOSTAT_MODE_COOLER_BY_DEFAULT false + +struct temp_t { + float temp; + unsigned long last_update = 0; + bool need_display_update = false; +}; + +struct temp_range_t { + int min = THERMOSTAT_TEMP_RANGE_MIN; + int max = THERMOSTAT_TEMP_RANGE_MAX; + unsigned long last_update = 0; + unsigned long ask_time = 0; + unsigned long ask_interval = ASK_TEMP_RANGE_INTERVAL_INITIAL; + bool need_display_update = true; +}; + using thermostat_callback_f = std::function; void thermostatRegister(thermostat_callback_f callback); + +const temp_t& thermostatRemoteTemp(); +const temp_range_t& thermostatRange(); + +void thermostatEnabled(bool enabled); +bool thermostatEnabled(); + +void thermostatModeCooler(bool cooler); +bool thermostatModeCooler(); + +void thermostatSetup(); diff --git a/code/espurna/thinkspeak.ino b/code/espurna/thingspeak.cpp similarity index 97% rename from code/espurna/thinkspeak.ino rename to code/espurna/thingspeak.cpp index 3d73c84c..d71ffeb2 100644 --- a/code/espurna/thinkspeak.ino +++ b/code/espurna/thingspeak.cpp @@ -6,12 +6,16 @@ Copyright (C) 2019 by Xose Pérez */ +#include "thingspeak.h" + #if THINGSPEAK_SUPPORT #include #include "broker.h" -#include "thingspeak.h" +#include "relay.h" +#include "sensor.h" +#include "ws.h" #include "libs/URL.h" #include "libs/SecureClientHelpers.h" #include "libs/AsyncClientHelpers.h" @@ -107,18 +111,20 @@ void _tspkWebSocketOnConnected(JsonObject& root) { root["tspkAddress"] = getSetting("tspkAddress", THINGSPEAK_ADDRESS); JsonArray& relays = root.createNestedArray("tspkRelays"); - for (byte i=0; i #pragma once +#include "espurna.h" + #if THINGSPEAK_SUPPORT #if THINGSPEAK_USE_ASYNC diff --git a/code/espurna/timelibshim.h b/code/espurna/timelibshim.h deleted file mode 100644 index 55dc4b17..00000000 --- a/code/espurna/timelibshim.h +++ /dev/null @@ -1,120 +0,0 @@ -// Shim original TimeLib functions - -#pragma once - -constexpr time_t daysPerWeek = 7; - -constexpr time_t secondsPerMinute = 60; -constexpr time_t secondsPerHour = 3600; -constexpr time_t secondsPerDay = secondsPerHour * 24; -constexpr time_t secondsPerWeek = daysPerWeek * secondsPerDay; - -constexpr time_t secondsPerYear = secondsPerWeek * 52; -constexpr time_t secondsY2K = 946684800; // the time at the start of y2k - -// wall clock values -constexpr const time_t numberOfSeconds(uint32_t ts) { - return (ts % secondsPerMinute); -} - -constexpr const time_t numberOfMinutes(uint32_t ts) { - return ((ts / secondsPerMinute) % secondsPerMinute); -} - -constexpr const time_t numberOfHours(uint32_t ts) { - return ((ts % secondsPerDay) / secondsPerHour); -} - -// week starts with sunday as number 1, monday as 2 etc. -constexpr const time_t dayOfWeek(time_t ts) { - return ((ts / secondsPerDay + 4) % daysPerWeek) + 1; -} - -// the number of days since 0 (Jan 1 1970 in case of time_t values) -constexpr const time_t elapsedDays(uint32_t ts) { - return (ts / secondsPerDay); -} - -// the number of seconds since last midnight -constexpr const time_t elapsedSecsToday(uint32_t ts) { - return (ts % secondsPerDay); -} - -// The following methods are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 -// Always set the correct time before settting alarms - -// time at the start of the given day -constexpr const time_t previousMidnight(time_t ts) { - return ((ts / secondsPerDay) * secondsPerDay); -} - -// time at the end of the given day -constexpr const time_t nextMidnight(time_t ts) { - return previousMidnight(ts) + secondsPerDay; -} - -// note that week starts on day 1 -constexpr const time_t elapsedSecsThisWeek(uint32_t ts) { - return elapsedSecsToday(ts) + ((dayOfWeek(ts) - 1) * secondsPerDay); -} - -// time at the start of the week for the given time -constexpr const time_t previousSunday(time_t ts) { - return ts - elapsedSecsThisWeek(ts); -} - -// time at the end of the week for the given time -constexpr const time_t nextSunday(time_t ts) { - return previousSunday(ts) + secondsPerWeek; -} - - year(t), month(t), day(t), hour(t), minute(t), second(t) - -static time_t _ntp_ts = 0; -static tm _ntp_tm; - -void _ntpTmCache(time_t ts) { - if (_ntp_tm != ts) { - _ntp_ts = ts; - localtime_r(_ntp_ts, _ntp_tm); - } -} - -int hour(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_hour; -} - -int minute(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_min; -} - -int second(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_sec; -} - -int day(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_day; -} - -int weekday(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_wday; -} - -int month(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_mon; -} - -int year(time_t ts) { - _ntpTmCache(ts); - return _ntp_tm.tm_year; -} - -time_t now() { - return time(nullptr); -} diff --git a/code/espurna/tuya.ino b/code/espurna/tuya.cpp similarity index 98% rename from code/espurna/tuya.ino rename to code/espurna/tuya.cpp index 6857888b..e42878dd 100644 --- a/code/espurna/tuya.ino +++ b/code/espurna/tuya.cpp @@ -8,8 +8,11 @@ Copyright (C) 2019 by Maxim Prokhorov // ref: https://docs.tuya.com/en/mcu/mcu-protocol.html +#include "tuya.h" + #if TUYA_SUPPORT +#include "broker.h" #include "relay.h" #include "light.h" @@ -25,15 +28,15 @@ Copyright (C) 2019 by Maxim Prokhorov namespace Tuya { - constexpr const size_t SERIAL_SPEED { 9600u }; + constexpr size_t SERIAL_SPEED { 9600u }; - constexpr const unsigned char SWITCH_MAX { 8u }; - constexpr const unsigned char DIMMER_MAX { 5u }; + constexpr unsigned char SWITCH_MAX { 8u }; + constexpr unsigned char DIMMER_MAX { 5u }; - constexpr const uint32_t DISCOVERY_TIMEOUT { 1500u }; + constexpr uint32_t DISCOVERY_TIMEOUT { 1500u }; - constexpr const uint32_t HEARTBEAT_SLOW { 9000u }; - constexpr const uint32_t HEARTBEAT_FAST { 3000u }; + constexpr uint32_t HEARTBEAT_SLOW { 9000u }; + constexpr uint32_t HEARTBEAT_FAST { 3000u }; // -------------------------------------------- diff --git a/code/espurna/tuya.h b/code/espurna/tuya.h index eb98e11f..7493083f 100644 --- a/code/espurna/tuya.h +++ b/code/espurna/tuya.h @@ -4,6 +4,8 @@ #pragma once +#include "espurna.h" + namespace Tuya { void tuyaSendChannel(unsigned char, unsigned int); void tuyaSendSwitch(unsigned char, bool); @@ -12,14 +14,3 @@ namespace Tuya { void tuyaSyncSwitchStatus(); void tuyaSetupSwitch(); } - -using Tuya::tuyaSetup; -using Tuya::tuyaSetupSwitch; -using Tuya::tuyaSyncSwitchStatus; -using Tuya::tuyaSendSwitch; - -#if LIGHT_PROVIDER == LIGHT_PROVIDER_TUYA - using Tuya::tuyaSetupLight; - using Tuya::tuyaSendChannel; -#endif - diff --git a/code/espurna/tuya_dataframe.h b/code/espurna/tuya_dataframe.h index 5aa419c1..6459df25 100644 --- a/code/espurna/tuya_dataframe.h +++ b/code/espurna/tuya_dataframe.h @@ -8,6 +8,7 @@ Copyright (C) 2019 by Maxim Prokhorov #pragma once +#include #include #include "tuya_types.h" diff --git a/code/espurna/tuya_protocol.h b/code/espurna/tuya_protocol.h index 3adbee36..bad394ac 100644 --- a/code/espurna/tuya_protocol.h +++ b/code/espurna/tuya_protocol.h @@ -8,6 +8,8 @@ Copyright (C) 2019 by Maxim Prokhorov #pragma once +#include + #include "tuya_dataframe.h" #include "tuya_types.h" #include "tuya_transport.h" diff --git a/code/espurna/tuya_util.h b/code/espurna/tuya_util.h index 8f46a439..457a2685 100644 --- a/code/espurna/tuya_util.h +++ b/code/espurna/tuya_util.h @@ -8,6 +8,7 @@ Copyright (C) 2019 by Maxim Prokhorov #pragma once +#include #include #include diff --git a/code/espurna/uartmqtt.ino b/code/espurna/uartmqtt.cpp similarity index 99% rename from code/espurna/uartmqtt.ino rename to code/espurna/uartmqtt.cpp index c0424dc4..3219173a 100644 --- a/code/espurna/uartmqtt.ino +++ b/code/espurna/uartmqtt.cpp @@ -7,10 +7,11 @@ Adapted by Xose Pérez */ +#include "uartmqtt.h" + #if UART_MQTT_SUPPORT #include "mqtt.h" -#include "uartmqtt.h" char _uartmqttBuffer[UART_MQTT_BUFFER_SIZE]; bool _uartmqttNewData = false; diff --git a/code/espurna/uartmqtt.h b/code/espurna/uartmqtt.h index 797857f2..0c9574e2 100644 --- a/code/espurna/uartmqtt.h +++ b/code/espurna/uartmqtt.h @@ -9,6 +9,8 @@ Adapted by Xose Pérez #pragma once +#include "espurna.h" + #if UART_MQTT_SUPPORT #include diff --git a/code/espurna/utils.ino b/code/espurna/utils.cpp similarity index 89% rename from code/espurna/utils.ino rename to code/espurna/utils.cpp index b5694e30..d594cf76 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.cpp @@ -8,14 +8,18 @@ Copyright (C) 2017-2019 by Xose Pérez #include -#include "config/buildtime.h" - -#include "board.h" -#include "mqtt.h" -#include "ntp.h" #include "utils.h" -#include "libs/HeapStats.h" +#include "board.h" +#include "influxdb.h" +#include "light.h" +#include "mqtt.h" +#include "ntp.h" +#include "relay.h" +#include "thermostat.h" + +#include "libs/TypeChecks.h" + //-------------------------------------------------------------------------------- // Reset reasons @@ -144,6 +148,74 @@ unsigned long getUptime() { } +//-------------------------------------------------------------------------------- +// Heap stats +//-------------------------------------------------------------------------------- + +namespace { + +template +using has_getHeapStats_t = decltype(std::declval().getHeapStats(0,0,0)); + +template +using has_getHeapStats = is_detected; + +template +void _getHeapStats(const std::true_type&, T& instance, heap_stats_t& stats) { + instance.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct); +} + +template +void _getHeapStats(const std::false_type&, T& instance, heap_stats_t& stats) { + stats.available = instance.getFreeHeap(); + stats.usable = 0; + stats.frag_pct = 0; +} + +} // namespace anonymous + +void getHeapStats(heap_stats_t& stats) { + _getHeapStats(has_getHeapStats{}, ESP, stats); +} + +// WTF +// Calling ESP.getFreeHeap() is making the system crash on a specific +// AiLight bulb, but anywhere else it should work as expected +static bool _heap_value_wtf = false; + +heap_stats_t getHeapStats() { + heap_stats_t stats; + if (_heap_value_wtf) { + stats.available = 9999; + stats.usable = 9999; + stats.frag_pct = 0; + return stats; + } + getHeapStats(stats); + return stats; +} + +void wtfHeap(bool value) { + _heap_value_wtf = value; +} + +unsigned int getFreeHeap() { + return ESP.getFreeHeap(); +} + +// TODO: place in struct ctor to run at the earliest opportunity +static unsigned int _initial_heap_value = 0; +void setInitialFreeHeap() { + _initial_heap_value = getFreeHeap(); +} + +unsigned int getInitialFreeHeap() { + if (0 == _initial_heap_value) { + setInitialFreeHeap(); + } + return _initial_heap_value; +} + // ----------------------------------------------------------------------------- // Heartbeat helper // ----------------------------------------------------------------------------- @@ -199,12 +271,7 @@ namespace Heartbeat { uint32_t currentValue() { // use default without any setting / when it is empty - const String cfg = getSetting("hbReport"); - if (!cfg.length()) { - return defaultValue(); - } - - const auto value = u32fromString(cfg); + const auto value = getSetting("hbReport", defaultValue()); // because we start shifting from 1, we could use the // first bit as a flag to enable all of the messages @@ -331,14 +398,16 @@ void heartbeat() { #if THERMOSTAT_SUPPORT if (hb_cfg & Heartbeat::Range) { - mqttSend(MQTT_TOPIC_HOLD_TEMP "_" MQTT_TOPIC_HOLD_TEMP_MIN, String(_temp_range.min).c_str()); - mqttSend(MQTT_TOPIC_HOLD_TEMP "_" MQTT_TOPIC_HOLD_TEMP_MAX, String(_temp_range.max).c_str()); + const auto& range = thermostatRange(); + mqttSend(MQTT_TOPIC_HOLD_TEMP "_" MQTT_TOPIC_HOLD_TEMP_MIN, String(range.min).c_str()); + mqttSend(MQTT_TOPIC_HOLD_TEMP "_" MQTT_TOPIC_HOLD_TEMP_MAX, String(range.max).c_str()); } if (hb_cfg & Heartbeat::RemoteTemp) { - char remote_temp[16]; - dtostrf(_remote_temp.temp, 1, 1, remote_temp); - mqttSend(MQTT_TOPIC_REMOTE_TEMP, remote_temp); + const auto& remote_temp = thermostatRemoteTemp(); + char buffer[16]; + dtostrf(remote_temp.temp, 1, 1, buffer); + mqttSend(MQTT_TOPIC_REMOTE_TEMP, buffer); } #endif @@ -427,6 +496,27 @@ void infoMemory(const char * name, unsigned int total_memory, unsigned int free_ } +void infoMemory(const char* name, const heap_stats_t& stats) { + infoMemory(name, getInitialFreeHeap(), stats.available); +} + +void infoHeapStats(const char* name, const heap_stats_t& stats) { + DEBUG_MSG_P( + PSTR("[MAIN] %-6s: %5u contiguous bytes available (%u%% fragmentation)\n"), + name, + stats.usable, + stats.frag_pct + ); +} + +void infoHeapStats(bool show_frag_stats) { + const auto stats = getHeapStats(); + infoMemory("Heap", stats); + if (show_frag_stats && has_getHeapStats{}) { + infoHeapStats("Heap", stats); + } +} + const char* _info_wifi_sleep_mode(WiFiSleepType_t type) { switch (type) { case WIFI_NONE_SLEEP: return "NONE"; @@ -701,57 +791,3 @@ char* strnstr(const char* buffer, const char* token, size_t n) { return nullptr; } - -// 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; - } - - 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; - } - } - - return u32fromString((base == 10) ? string : string.substring(2), base); -} - -String u32toString(uint32_t value, int base) { - String result; - result.reserve(32 + 2); - - if (base == 2) { - result += "0b"; - } else if (base == 8) { - result += "0o"; - } else if (base == 16) { - result += "0x"; - } - - char buffer[33] = {0}; - ultoa(value, buffer, base); - result += buffer; - - return result; -} diff --git a/code/espurna/utils.h b/code/espurna/utils.h index 35b84273..0134028e 100644 --- a/code/espurna/utils.h +++ b/code/espurna/utils.h @@ -8,6 +8,14 @@ Copyright (C) 2017-2019 by Xose Pérez #pragma once +#include "espurna.h" + +struct heap_stats_t { + uint32_t available; + uint16_t usable; + uint8_t frag_pct; +}; + PROGMEM const char pstr_unknown[] = "UNKNOWN"; #define INLINE inline __attribute__((always_inline)) @@ -28,10 +36,21 @@ int getHeartbeatMode(); unsigned long getHeartbeatInterval(); void heartbeat(); +String getAdminPass(); +String getBoardName(); String buildTime(); unsigned long getUptime(); bool haveRelaysOrSensors(); +void getHeapStats(heap_stats_t& stats); +heap_stats_t getHeapStats(); +void wtfHeap(bool value); +unsigned int getFreeHeap(); +void setInitialFreeHeap(); +unsigned int getInitialFreeHeap(); + +void infoHeapStats(const char* name, const heap_stats_t& stats); +void infoHeapStats(bool show_frag_stats = true); void infoMemory(const char * name, unsigned int total_memory, unsigned int free_memory); void infoUptime(); void info(bool first = false); @@ -49,7 +68,3 @@ bool isNumber(const char * s); void nice_delay(unsigned long ms); double roundTo(double num, unsigned char positions); - -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/web.ino b/code/espurna/web.cpp similarity index 99% rename from code/espurna/web.ino rename to code/espurna/web.cpp index b8eb7cb2..e4991743 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.cpp @@ -6,11 +6,13 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "web.h" + #if WEB_SUPPORT #include "system.h" #include "utils.h" -#include "web.h" +#include "ntp.h" #if WEB_EMBEDDED diff --git a/code/espurna/web.h b/code/espurna/web.h index 727016f1..83769d02 100644 --- a/code/espurna/web.h +++ b/code/espurna/web.h @@ -8,10 +8,12 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once -#include +#include "espurna.h" #if WEB_SUPPORT +#include + #include #include #include @@ -19,23 +21,18 @@ Copyright (C) 2016-2019 by Xose Pérez #include #include -#else - -// TODO: need these prototypes for .ino -class AsyncClient; -class AsyncWebServer; -class AsyncWebServerRequest; -class ArRequestHandlerFunction; -class AsyncWebSocketClient; -class AsyncWebSocket; -class AwsEventType; - -#endif // WEB_SUPPORT == 1 - using web_body_callback_f = std::function; using web_request_callback_f = std::function; AsyncWebServer* webServer(); +bool webAuthenticate(AsyncWebServerRequest *request); +void webLog(AsyncWebServerRequest *request); + void webBodyRegister(web_body_callback_f); void webRequestRegister(web_request_callback_f); + +uint16_t webPort(); +void webSetup(); + +#endif // WEB_SUPPORT == 1 diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.cpp similarity index 99% rename from code/espurna/wifi.ino rename to code/espurna/wifi.cpp index c7c21104..249b8d36 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.cpp @@ -6,14 +6,12 @@ Copyright (C) 2016-2019 by Xose Pérez */ -#include -#include - -#include "ws.h" - #include "wifi.h" #include "wifi_config.h" +#include "telnet.h" +#include "ws.h" + bool _wifi_wps_running = false; bool _wifi_smartconfig_running = false; bool _wifi_smartconfig_initial = false; @@ -37,6 +35,8 @@ struct wifi_scan_info_t { char buffer[128]; }; +using wifi_scan_f = std::function; + void _wifiUpdateSoftAP() { if (WiFi.softAPgetStationNum() == 0) { #if USE_PASSWORD diff --git a/code/espurna/wifi.h b/code/espurna/wifi.h index bb83d067..da25b669 100644 --- a/code/espurna/wifi.h +++ b/code/espurna/wifi.h @@ -8,8 +8,18 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + +#include +#if LWIP_VERSION_MAJOR == 1 +#include +#elif LWIP_VERSION_MAJOR >= 2 +#include +#endif + #define LWIP_INTERNAL -#include +#include +#include #undef LWIP_INTERNAL extern "C" { @@ -20,26 +30,14 @@ extern "C" { #include // ERR_x #include // dns_gethostbyname #include // ip4/ip6 helpers - #include // LWIP_VERSION_MAJOR }; -#if LWIP_VERSION_MAJOR == 1 -#include -#else // LWIP_VERSION_MAJOR >= 2 -#include -#endif - -#include - // ref: https://github.com/me-no-dev/ESPAsyncTCP/pull/115/files#diff-e2e636049095cc1ff920c1bfabf6dcacR8 // This is missing with Core 2.3.0 and is sometimes missing from the build flags. Assume HIGH_BANDWIDTH version. #ifndef TCP_MSS #define TCP_MSS (1460) #endif -struct wifi_scan_info_t; - -using wifi_scan_f = std::function; using wifi_callback_f = std::function; uint8_t wifiState(); diff --git a/code/espurna/wifi_config.h b/code/espurna/wifi_config.h index c02b025c..9d08ef62 100644 --- a/code/espurna/wifi_config.h +++ b/code/espurna/wifi_config.h @@ -8,7 +8,7 @@ Copyright (C) 2020 by Maxim Prokhorov #pragma once -#include +#include "espurna.h" constexpr bool _wifiHasSSID(unsigned char index) { return ( diff --git a/code/espurna/ws.ino b/code/espurna/ws.cpp similarity index 99% rename from code/espurna/ws.ino rename to code/espurna/ws.cpp index d25129a4..e451b67a 100644 --- a/code/espurna/ws.ino +++ b/code/espurna/ws.cpp @@ -6,14 +6,16 @@ Copyright (C) 2016-2019 by Xose Pérez */ +#include "ws.h" + #if WEB_SUPPORT #include #include "system.h" #include "web.h" +#include "ntp.h" #include "utils.h" -#include "ws.h" #include "ws_internal.h" #include "libs/WebSocketIncommingBuffer.h" @@ -51,8 +53,8 @@ ws_callbacks_t& ws_callbacks_t::onKeyCheck(ws_on_keycheck_callback_f cb) { return *this; } -ws_callbacks_t _ws_callbacks; -std::queue _ws_client_data; +static ws_callbacks_t _ws_callbacks; +static std::queue _ws_client_data; // ----------------------------------------------------------------------------- // WS authentication diff --git a/code/espurna/ws.h b/code/espurna/ws.h index ae69f1cd..538f756f 100644 --- a/code/espurna/ws.h +++ b/code/espurna/ws.h @@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #include #include @@ -47,22 +49,23 @@ void wsSend(uint32_t client_id, const char* data); void wsSend(uint32_t client_id, JsonObject& root); void wsSend(JsonObject& root); void wsSend(ws_on_send_callback_f callback); +void wsSend(const char* data); void wsSend_P(PGM_P data); void wsSend_P(uint32_t client_id, PGM_P data); -void INLINE wsPost(const ws_on_send_callback_f& callback); -void INLINE wsPost(uint32_t client_id, const ws_on_send_callback_f& callback); -void INLINE wsPost(const ws_on_send_callback_list_t& callbacks); -void INLINE wsPost(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); +void wsPost(const ws_on_send_callback_f& callback); +void wsPost(uint32_t client_id, const ws_on_send_callback_f& callback); +void wsPost(const ws_on_send_callback_list_t& callbacks); +void wsPost(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); -void INLINE wsPostAll(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); -void INLINE wsPostAll(const ws_on_send_callback_list_t& callbacks); +void wsPostAll(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); +void wsPostAll(const ws_on_send_callback_list_t& callbacks); -void INLINE wsPostSequence(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); -void INLINE wsPostSequence(uint32_t client_id, ws_on_send_callback_list_t&& callbacks); -void INLINE wsPostSequence(const ws_on_send_callback_list_t& callbacks); +void wsPostSequence(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); +void wsPostSequence(uint32_t client_id, ws_on_send_callback_list_t&& callbacks); +void wsPostSequence(const ws_on_send_callback_list_t& callbacks); -bool INLINE wsConnected(); -bool INLINE wsConnected(uint32_t client_id); +bool wsConnected(); +bool wsConnected(uint32_t client_id); bool wsDebugSend(const char* prefix, const char* message); diff --git a/code/espurna/ws_internal.h b/code/espurna/ws_internal.h index bf0b068c..c8242237 100644 --- a/code/espurna/ws_internal.h +++ b/code/espurna/ws_internal.h @@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez #pragma once +#include "espurna.h" + #include #include diff --git a/code/scripts/espurna_utils/release.py b/code/scripts/espurna_utils/release.py index 9ffd9842..deee4ef0 100644 --- a/code/scripts/espurna_utils/release.py +++ b/code/scripts/espurna_utils/release.py @@ -1,5 +1,17 @@ +import atexit import os import shutil +import tempfile + +from .display import print_warning + + +def try_remove(path): + try: + os.remove(path) + except: # pylint: disable=bare-except + print_warning("Please manually remove the file `{}`".format(path)) + def copy_release(target, source, env): # target filename and subdir for release files @@ -20,3 +32,18 @@ def copy_release(target, source, env): shutil.copy(src, dest) + +# emulate .ino concatenation to speed up compilation times +def merge_cpp(sources, output): + with tempfile.TemporaryFile() as tmp: + tmp.write(b"// !!! Automatically generated file; DO NOT EDIT !!! \n") + tmp.write(b'#include "espurna.h"\n') + for source in sources: + with open(source, "rb") as fobj: + shutil.copyfileobj(fobj, tmp) + + tmp.seek(0) + + with open(output, "wb") as fobj: + shutil.copyfileobj(tmp, fobj) + atexit.register(try_remove, output) diff --git a/code/scripts/generate_release_sh.py b/code/scripts/generate_release_sh.py index baed6aad..ebfe8e26 100755 --- a/code/scripts/generate_release_sh.py +++ b/code/scripts/generate_release_sh.py @@ -97,6 +97,7 @@ def generate_lines(builds, ignore): if build.src_build_flags: flags.append('ESPURNA_FLAGS="{}"'.format(build.src_build_flags)) flags.append('ESPURNA_RELEASE_NAME="{env}"'.format(env=build.env)) + flags.append("ESPURNA_BUILD_SINGLE_SOURCE=1") cmd = ["env"] cmd.extend(flags) diff --git a/code/scripts/pio_main.py b/code/scripts/pio_main.py index 0661d15b..2113e75c 100644 --- a/code/scripts/pio_main.py +++ b/code/scripts/pio_main.py @@ -20,9 +20,11 @@ from espurna_utils import ( copy_release, ) + Import("env", "projenv") import os + CI = any([os.environ.get("TRAVIS"), os.environ.get("CI")]) # Always show warnings for project code diff --git a/code/scripts/pio_pre.py b/code/scripts/pio_pre.py index e280bba0..92f9367c 100644 --- a/code/scripts/pio_pre.py +++ b/code/scripts/pio_pre.py @@ -19,6 +19,8 @@ import sys from SCons.Script import ARGUMENTS +from espurna_utils.release import merge_cpp + CI = any([os.environ.get("TRAVIS"), os.environ.get("CI")]) PIO_PLATFORM = env.PioPlatform() @@ -99,7 +101,7 @@ def ensure_platform_updated(): env.Append( ESPURNA_BOARD=os.environ.get("ESPURNA_BOARD", ""), ESPURNA_AUTH=os.environ.get("ESPURNA_AUTH", ""), - ESPURNA_FLAGS=os.environ.get("ESPURNA_FLAGS", "") + ESPURNA_FLAGS=os.environ.get("ESPURNA_FLAGS", ""), ) ESPURNA_OTA_PORT = os.environ.get("ESPURNA_IP") @@ -115,7 +117,7 @@ if CI: env.Append( ESPURNA_RELEASE_NAME=os.environ.get("ESPURNA_RELEASE_NAME", ""), ESPURNA_RELEASE_VERSION=os.environ.get("ESPURNA_RELEASE_VERSION", ""), - ESPURNA_RELEASE_DESTINATION=os.environ.get("ESPURNA_RELEASE_DESTINATION", "") + ESPURNA_RELEASE_DESTINATION=os.environ.get("ESPURNA_RELEASE_DESTINATION", ""), ) # updates arduino core git to the latest master commit @@ -136,3 +138,25 @@ if os.environ.get("ESPURNA_PIO_SHARED_LIBRARIES"): log("using shared library storage: {}".format(storage)) subprocess_libdeps(env.GetProjectOption("lib_deps"), storage) + +# tweak build system to ignore espurna.ino, but include user code +# ref: platformio-core/platformio/tools/piomisc.py::ConvertInoToCpp() +def ConvertInoToCpp(env): + pass + + +ino = env.Glob("$PROJECT_DIR/espurna/*.ino") + env.Glob("$PROJECT_DIR/espurna/*.pde") +if len(ino) == 1 and ino[0].name == "espurna.ino": + env.AddMethod(ConvertInoToCpp) + +# merge every .cpp into a single file and **only** build that single file +if os.environ.get("ESPURNA_BUILD_SINGLE_SOURCE"): + cpp_files = [] + for root, dirs, filenames in os.walk("espurna"): + for name in filenames: + if not name.endswith(".cpp"): + continue + path = os.path.join(root, name) + env.AddBuildMiddleware(lambda node: None, "*?/{}".format(path)) + cpp_files.append(path) + merge_cpp(cpp_files, "espurna/espurna_single_source.cpp") diff --git a/code/scripts/test_build.py b/code/scripts/test_build.py index c65c984a..1587efe3 100755 --- a/code/scripts/test_build.py +++ b/code/scripts/test_build.py @@ -25,52 +25,45 @@ import subprocess import os import sys import datetime -from espurna_utils.display import Color, clr, print_warning -CUSTOM_HEADER = "espurna/config/custom.h" -if os.path.exists(CUSTOM_HEADER): - raise SystemExit( - clr( - Color.YELLOW, - "{} already exists, please run this script in a git-worktree(1) or a separate directory".format( - CUSTOM_HEADER - ), - ) - ) +from espurna_utils.display import clr, print_warning, Color + + +def restore_source_tree(files): + cmd = ["git", "checkout", "-f", "--"] + cmd.extend(files) + subprocess.check_call(cmd) def try_remove(path): try: os.remove(path) - except: # pylint: disable=bare-except + except: # pylint: disable=bare-except print_warning("Please manually remove the file `{}`".format(path)) -atexit.register(try_remove, CUSTOM_HEADER) +total_time = 0 -def main(args): - configurations = [] - if not args.no_default: - configurations = list(glob.glob(args.default_configurations)) +def print_total_time(): + print() + print( + clr( + Color.BOLD, + "> Total time: {}".format(datetime.timedelta(seconds=total_time)), + ) + ) - configurations.extend(x for x in (args.add or [])) - if not configurations: - raise SystemExit(clr(Color.YELLOW, "No configurations selected")) - print(clr(Color.BOLD, "> Selected configurations:")) - for cfg in configurations: - print(cfg) - if args.list: - return - - if not args.environment: - raise SystemExit(clr(Color.YELLOW, "No environment selected")) - print(clr(Color.BOLD, "> Selected environment: {}".format(args.environment))) +def run_configurations(args, configurations): + cmd = ["platformio", "run"] + if not args.no_silent: + cmd.extend(["-s"]) + cmd.extend(["-e", args.environment]) for cfg in configurations: print(clr(Color.BOLD, "> Building {}".format(cfg))) - with open(CUSTOM_HEADER, "w") as custom_h: + with open(args.custom_h, "w") as custom_h: def write(line): sys.stdout.write(line) @@ -87,11 +80,15 @@ def main(args): os_env = os.environ.copy() os_env["PLATFORMIO_SRC_BUILD_FLAGS"] = "-DUSE_CUSTOM_H" os_env["PLATFORMIO_BUILD_CACHE_DIR"] = "test/pio_cache" - cmd = ["platformio", "run", "-s", "-e", args.environment] + os_env["ESPURNA_BUILD_SINGLE_SOURCE"] = "1" start = time.time() subprocess.check_call(cmd, env=os_env) - end = time.time() + diff = time.time() - start + + global total_time + total_time += diff + print( clr( Color.BOLD, @@ -100,17 +97,59 @@ def main(args): os.stat( os.path.join(".pio", "build", args.environment, "firmware.bin") ).st_size, - datetime.timedelta(seconds=(end - start)), + datetime.timedelta(seconds=diff), ), ) ) +def main(args): + if os.path.exists(args.custom_h): + raise SystemExit( + clr( + Color.YELLOW, + "{} already exists, please run this script in a git-worktree(1) or a separate directory".format( + args.custom_h + ), + ) + ) + + configurations = [] + if not args.no_default: + configurations = list(glob.glob(args.default_configurations)) + + configurations.extend(x for x in (args.add or [])) + if not configurations: + raise SystemExit(clr(Color.YELLOW, "No configurations selected")) + + if len(configurations) > 1: + atexit.register(print_total_time) + + print(clr(Color.BOLD, "> Selected configurations:")) + for cfg in configurations: + print(cfg) + if args.list: + return + + if not args.environment: + raise SystemExit(clr(Color.YELLOW, "No environment selected")) + print(clr(Color.BOLD, "> Selected environment: {}".format(args.environment))) + + atexit.register(try_remove, args.custom_h) + + run_configurations(args, configurations) + + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-l", "--list", action="store_true", help="List selected configurations" ) + parser.add_argument( + "--custom-h", + default="espurna/config/custom.h", + help="Header that will be included in by the config/all.h", + ) parser.add_argument( "-n", "--no-default", @@ -129,5 +168,8 @@ if __name__ == "__main__": default="test/build/*.h", help="(glob) default configuration headers", ) + parser.add_argument( + "--no-silent", action="store_true", help="Do not silence pio-run" + ) main(parser.parse_args())