Browse Source

Convert .ino -> .cpp (#2228)

- general conversion from .ino modules into a separate .cpp files
- clean-up internal headers, place libraries into .h. guard .cpp with _SUPPORT flags 
- fix some instances of shared variables instead of public methods
- tweak build system to still build a single source file via os environment variable ESPURNA_BUILD_SINGLE_SOURCE
mcspr-patch-1
Max Prokhorov 4 years ago
committed by GitHub
parent
commit
edb23dbfc4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
125 changed files with 2960 additions and 2335 deletions
  1. +38
    -29
      code/espurna/alexa.cpp
  2. +1
    -7
      code/espurna/alexa.h
  3. +6
    -3
      code/espurna/api.cpp
  4. +7
    -10
      code/espurna/api.h
  5. +2
    -0
      code/espurna/board.cpp
  6. +2
    -0
      code/espurna/board.h
  7. +1
    -6
      code/espurna/broker.h
  8. +92
    -89
      code/espurna/button.cpp
  9. +2
    -0
      code/espurna/button.h
  10. +2
    -0
      code/espurna/button_config.h
  11. +28
    -0
      code/espurna/compat.h
  12. +2
    -1
      code/espurna/crash.cpp
  13. +5
    -1
      code/espurna/crash.h
  14. +6
    -3
      code/espurna/debug.cpp
  15. +6
    -2
      code/espurna/debug.h
  16. +23
    -20
      code/espurna/domoticz.cpp
  17. +2
    -1
      code/espurna/domoticz.h
  18. +0
    -0
      code/espurna/encoder.cpp
  19. +49
    -0
      code/espurna/espurna.h
  20. +19
    -276
      code/espurna/espurna.ino
  21. +0
    -0
      code/espurna/gpio.cpp
  22. +3
    -0
      code/espurna/gpio.h
  23. +4
    -1
      code/espurna/homeassistant.cpp
  24. +2
    -0
      code/espurna/homeassistant.h
  25. +2
    -0
      code/espurna/i2c.cpp
  26. +6
    -0
      code/espurna/i2c.h
  27. +4
    -0
      code/espurna/influxdb.cpp
  28. +21
    -0
      code/espurna/influxdb.h
  29. +3
    -1
      code/espurna/ir.cpp
  30. +2
    -0
      code/espurna/ir.h
  31. +2
    -0
      code/espurna/ir_button.h
  32. +75
    -71
      code/espurna/led.cpp
  33. +3
    -1
      code/espurna/led.h
  34. +2
    -0
      code/espurna/led_config.h
  35. +2
    -2
      code/espurna/led_pattern.h
  36. +2
    -2
      code/espurna/led_pattern.h.in
  37. +2
    -0
      code/espurna/libs/BasePin.h
  38. +0
    -97
      code/espurna/libs/HeapStats.h
  39. +0
    -33
      code/espurna/libs/NtpClientWrap.h
  40. +0
    -37
      code/espurna/libs/RFM69Wrap.h
  41. +6
    -1
      code/espurna/libs/SecureClientHelpers.h
  42. +50
    -54
      code/espurna/libs/URL.h
  43. +276
    -254
      code/espurna/light.cpp
  44. +30
    -23
      code/espurna/light.h
  45. +2
    -0
      code/espurna/light_config.h
  46. +0
    -0
      code/espurna/lightfox.cpp
  47. +2
    -2
      code/espurna/llmnr.cpp
  48. +16
    -0
      code/espurna/llmnr.h
  49. +315
    -0
      code/espurna/main.cpp
  50. +5
    -0
      code/espurna/mdns.cpp
  51. +11
    -0
      code/espurna/mdns.h
  52. +0
    -0
      code/espurna/migrate.cpp
  53. +84
    -88
      code/espurna/mqtt.cpp
  54. +27
    -4
      code/espurna/mqtt.h
  55. +2
    -2
      code/espurna/netbios.cpp
  56. +17
    -0
      code/espurna/netbios.h
  57. +23
    -19
      code/espurna/nofuss.cpp
  58. +17
    -0
      code/espurna/nofuss.h
  59. +8
    -5
      code/espurna/ntp.cpp
  60. +9
    -5
      code/espurna/ntp.h
  61. +47
    -19
      code/espurna/ntp_legacy.cpp
  62. +0
    -0
      code/espurna/ota.cpp
  63. +3
    -1
      code/espurna/ota.h
  64. +2
    -1
      code/espurna/ota_arduinoota.cpp
  65. +6
    -1
      code/espurna/ota_asynctcp.cpp
  66. +2
    -1
      code/espurna/ota_httpupdate.cpp
  67. +3
    -5
      code/espurna/ota_web.cpp
  68. +64
    -12
      code/espurna/relay.cpp
  69. +11
    -34
      code/espurna/relay.h
  70. +2
    -0
      code/espurna/relay_config.h
  71. +128
    -127
      code/espurna/rfbridge.cpp
  72. +2
    -0
      code/espurna/rfbridge.h
  73. +63
    -12
      code/espurna/rfm69.cpp
  74. +4
    -12
      code/espurna/rfm69.h
  75. +0
    -0
      code/espurna/rpc.cpp
  76. +2
    -0
      code/espurna/rpc.h
  77. +21
    -15
      code/espurna/rpnrules.cpp
  78. +1
    -2
      code/espurna/rpnrules.h
  79. +4
    -0
      code/espurna/rtcmem.cpp
  80. +7
    -1
      code/espurna/rtcmem.h
  81. +5
    -1
      code/espurna/scheduler.cpp
  82. +14
    -0
      code/espurna/scheduler.h
  83. +331
    -161
      code/espurna/sensor.cpp
  84. +3
    -177
      code/espurna/sensor.h
  85. +1
    -0
      code/espurna/sensors/I2CSensor.h
  86. +185
    -38
      code/espurna/settings.cpp
  87. +107
    -4
      code/espurna/settings.h
  88. +1
    -65
      code/espurna/settings_internal.h
  89. +3
    -0
      code/espurna/ssdp.cpp
  90. +20
    -0
      code/espurna/ssdp.h
  91. +1
    -1
      code/espurna/storage_eeprom.cpp
  92. +2
    -0
      code/espurna/storage_eeprom.h
  93. +8
    -2
      code/espurna/system.cpp
  94. +6
    -4
      code/espurna/system.h
  95. +4
    -1
      code/espurna/telnet.cpp
  96. +23
    -9
      code/espurna/telnet.h
  97. +9
    -3
      code/espurna/terminal.cpp
  98. +3
    -0
      code/espurna/terminal.h
  99. +122
    -151
      code/espurna/thermostat.cpp
  100. +47
    -0
      code/espurna/thermostat.h

code/espurna/alexa.ino → code/espurna/alexa.cpp View File

@ -6,19 +6,28 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "alexa.h"
#if ALEXA_SUPPORT
#include <queue>
#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;
};
fauxmoESP _alexa;
static std::queue<alexa_queue_element_t> _alexa_queue;
fauxmoESP _alexa;
// -----------------------------------------------------------------------------
// ALEXA
// -----------------------------------------------------------------------------
@ -76,6 +85,32 @@ bool alexaEnabled() {
return getSetting<bool>("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

+ 1
- 7
code/espurna/alexa.h View File

@ -8,13 +8,7 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#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


code/espurna/api.ino → code/espurna/api.cpp View File

@ -6,19 +6,22 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "api.h"
#if API_SUPPORT
#include "api.h"
#include <vector>
#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<web_api_t> _apis;
// -----------------------------------------------------------------------------

+ 7
- 10
code/espurna/api.h View File

@ -8,24 +8,21 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#include "web.h"
#include <functional>
// TODO: need these prototypes for .ino
using api_get_callback_f = std::function<void(char * buffer, size_t size)>;
using api_put_callback_f = std::function<void(const char * payload)> ;
#if API_SUPPORT
#if WEB_SUPPORT && API_SUPPORT
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <vector>
using api_get_callback_f = std::function<void(char * buffer, size_t size)>;
using api_put_callback_f = std::function<void(const char * payload)> ;
void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = nullptr);
#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

code/espurna/board.ino → code/espurna/board.cpp View File

@ -5,6 +5,8 @@ BOARD MODULE
*/
#include "board.h"
#include "relay.h"
#include "sensor.h"
//--------------------------------------------------------------------------------

+ 2
- 0
code/espurna/board.h View File

@ -6,6 +6,8 @@ BOARD MODULE
#pragma once
#include "espurna.h"
const String& getChipId();
const String& getIdentifier();


+ 1
- 6
code/espurna/broker.h View File

@ -8,7 +8,7 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#if BROKER_SUPPORT
#include "espurna.h"
#include <functional>
#include <vector>
@ -46,14 +46,9 @@ struct TBroker {
template <TBrokerType type, typename... TArgs>
TBrokerCallbacks<TArgs...> TBroker<type, TArgs...>::callbacks;
// --- Some known types. Bind them here to avoid .ino screwing with order ---
using StatusBroker = TBroker<TBrokerType::Status, const String&, unsigned char, unsigned int>;
using SensorReadBroker = TBroker<TBrokerType::SensorRead, const String&, unsigned char, double, const char*>;
using SensorReportBroker = TBroker<TBrokerType::SensorReport, const String&, unsigned char, double, const char*>;
using ConfigBroker = TBroker<TBrokerType::Config, const String&, const String&>;
#endif // BROKER_SUPPORT == 1

code/espurna/button.ino → code/espurna/button.cpp View File

@ -6,6 +6,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "button.h"
#if BUTTON_SUPPORT
#include <bitset>
@ -15,10 +17,11 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#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<int>(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<int>(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<relayCount(); i++) {
const bool status = (value & (1 << 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<relayCount(); i++) {
const bool status = (value & (1 << 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

+ 2
- 0
code/espurna/button.h View File

@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#include "libs/BasePin.h"
#include "libs/DebounceEvent.h"


+ 2
- 0
code/espurna/button_config.h View File

@ -6,6 +6,8 @@ BUTTON MODULE
#pragma once
#include "espurna.h"
namespace ButtonMask {
enum {


+ 28
- 0
code/espurna/compat.h View File

@ -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 <algorithm>
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 <memory>
namespace std {
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
}
#endif
#define UNUSED(x) (void)(x)


code/espurna/crash.ino → code/espurna/crash.cpp View File

@ -4,6 +4,8 @@
// https://github.com/krzychb/EspSaveCrash
// -----------------------------------------------------------------------------
#include "crash.h"
#if DEBUG_SUPPORT
#include <stdio.h>
@ -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;

+ 5
- 1
code/espurna/crash.h View File

@ -6,6 +6,11 @@
#pragma once
#include "espurna.h"
#include <Arduino.h>
#include <cstdint>
#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();

code/espurna/debug.ino → code/espurna/debug.cpp View File

@ -6,14 +6,17 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "debug.h"
#if DEBUG_SUPPORT
#include <limits>
#include <type_traits>
#include <vector>
#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));

+ 6
- 2
code/espurna/debug.h View File

@ -6,7 +6,11 @@ DEBUG MODULE
#pragma once
#include <pgmspace.h>
#include "espurna.h"
#if DEBUG_WEB_SUPPORT
#include <ArduinoJson.h>
#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, ...);


code/espurna/domoticz.ino → code/espurna/domoticz.cpp View File

@ -6,13 +6,16 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#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<RELAYS_MAX> _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
//------------------------------------------------------------------------------

+ 2
- 1
code/espurna/domoticz.h View File

@ -8,10 +8,11 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#if DOMOTICZ_SUPPORT
#include <ArduinoJson.h>
#include <bitset>
template<typename T>


code/espurna/encoder.ino → code/espurna/encoder.cpp View File


+ 49
- 0
code/espurna/espurna.h View File

@ -0,0 +1,49 @@
/*
ESPurna
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
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 <http://www.gnu.org/licenses/>.
*/
#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 <functional>
#include <algorithm>
#include <limits>
#include <vector>
#include <memory>
using void_callback_f = void (*)();
void espurnaRegisterLoop(void_callback_f callback);
void espurnaRegisterReload(void_callback_f callback);
void espurnaReload();
unsigned long espurnaLoopDelay();

+ 19
- 276
code/espurna/espurna.ino View File

@ -19,305 +19,48 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config/all.h"
// !!! NOTICE !!!
//
// This file is only for compatibility with Arduino IDE / arduino-cli
// See main.cpp
//
#include <functional>
#include <algorithm>
#include <limits>
#include <vector>
#include <memory>
#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<void_callback_f> _loop_callbacks;
std::vector<void_callback_f> _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<int>("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);
}

code/espurna/gpio.ino → code/espurna/gpio.cpp View File


+ 3
- 0
code/espurna/gpio.h View File

@ -8,6 +8,9 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include <cstdint>
#include "espurna.h"
#include "libs/BasePin.h"
constexpr const size_t GpioPins = 17;


code/espurna/homeassistant.ino → code/espurna/homeassistant.cpp View File

@ -6,15 +6,18 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "homeassistant.h"
#if HOMEASSISTANT_SUPPORT
#include <Ticker.h>
#include <Schedule.h>
#include "homeassistant.h"
#include "light.h"
#include "mqtt.h"
#include "relay.h"
#include "rpc.h"
#include "sensor.h"
#include "utils.h"
#include "ws.h"

+ 2
- 0
code/espurna/homeassistant.h View File

@ -8,6 +8,8 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#if HOMEASSISTANT_SUPPORT
#include <ArduinoJson.h>


code/espurna/i2c.ino → code/espurna/i2c.cpp View File

@ -6,6 +6,8 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "i2c.h"
#if I2C_SUPPORT
unsigned int _i2c_locked[16] = {0};

+ 6
- 0
code/espurna/i2c.h View File

@ -8,6 +8,8 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#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

code/espurna/influxdb.ino → code/espurna/influxdb.cpp View File

@ -6,12 +6,16 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "influxdb.h"
#if INFLUXDB_SUPPORT
#include <map>
#include <memory>
#include "broker.h"
#include "ws.h"
#include "terminal.h"
#include "libs/AsyncClientHelpers.h"
const char InfluxDb_http_success[] = "HTTP/1.1 204";

+ 21
- 0
code/espurna/influxdb.h View File

@ -0,0 +1,21 @@
/*
INFLUXDB MODULE
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "espurna.h"
#if INFLUXDB_SUPPORT
#include <ESPAsyncTCP.h>
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

code/espurna/ir.ino → code/espurna/ir.cpp View File

@ -46,9 +46,11 @@ Raw messages:
--------------------------------------------------------------------------------
*/
#include "ir.h"
#if IR_SUPPORT
#include "ir.h"
#include "light.h"
#include "mqtt.h"
#include "relay.h"

+ 2
- 0
code/espurna/ir.h View File

@ -10,6 +10,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#if IR_SUPPORT
#include "ir_button.h"


+ 2
- 0
code/espurna/ir_button.h View File

@ -10,6 +10,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
// Remote Buttons SET 1 (for the original Remote shipped with the controller)
#if IR_BUTTON_SET == 1


code/espurna/led.ino → code/espurna/led.cpp View File

@ -6,15 +6,18 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "led.h"
#if LED_SUPPORT
#include <algorithm>
#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

+ 3
- 1
code/espurna/led.h View File

@ -8,10 +8,12 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#include <vector>
#include <memory>
constexpr const size_t LedsMax = 8;
constexpr size_t LedsMax = 8;
enum class LedMode {
NetworkAutoconfig,


+ 2
- 0
code/espurna/led_config.h View File

@ -6,6 +6,8 @@ LED MODULE
#pragma once
#include "espurna.h"
constexpr const unsigned char _ledPin(unsigned char index) {
return (
(index == 0) ? LED1_PIN :


+ 2
- 2
code/espurna/led_pattern.h View File

@ -10,10 +10,10 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#pragma once
#include <cstring>
#include "led.h"
#include <cstring>
// Scans input string with format
// '<on1>,<off1>,<repeats1> <on2>,<off2>,<repeats2> ...'
// Directly changing `led.pattern.delays` contents


+ 2
- 2
code/espurna/led_pattern.h.in View File

@ -8,10 +8,10 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#pragma once
#include <cstring>
#include "led.h"
#include <cstring>
// Scans input string with format
// '<on1>,<off1>,<repeats1> <on2>,<off2>,<repeats2> ...'
// Directly changing `led.pattern.delays` contents


+ 2
- 0
code/espurna/libs/BasePin.h View File

@ -8,6 +8,8 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#pragma once
#include <cstdint>
// base interface for generic pin handler.
class BasePin {
public:


+ 0
- 97
code/espurna/libs/HeapStats.h View File

@ -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 <typename T>
using has_getHeapStats_t = decltype(std::declval<T>().getHeapStats(0,0,0));
template <typename T>
using has_getHeapStats = is_detected<has_getHeapStats_t, T>;
}
template <typename T>
void _getHeapStats(const std::true_type&, T& instance, heap_stats_t& stats) {
instance.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct);
}
template <typename T>
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<decltype(ESP)>{}, 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<decltype(ESP)>{}) {
infoHeapStats("Heap", stats);
}
}

+ 0
- 33
code/espurna/libs/NtpClientWrap.h View File

@ -1,33 +0,0 @@
// -----------------------------------------------------------------------------
// NtpClient overrides to avoid triggering network sync
// -----------------------------------------------------------------------------
#pragma once
#if NTP_LEGACY_SUPPORT
#include <WiFiUdp.h>
#include <NtpClientLib.h>
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

+ 0
- 37
code/espurna/libs/RFM69Wrap.h View File

@ -26,40 +26,3 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <RFM69_ATC.h>
#include <SPI.h>
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);
}
};

+ 6
- 1
code/espurna/libs/SecureClientHelpers.h View File

@ -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 <WiFiClientSecureBearSSL.h>
#elif SECURE_CLIENT == SECURE_CLIENT_AXTLS
@ -20,7 +24,8 @@ using fp_callback_f = std::function<String()>;
using cert_callback_f = std::function<const char*()>;
using mfln_callback_f = std::function<uint16_t()>;
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";


+ 50
- 54
code/espurna/libs/URL.h View File

@ -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)
{}
private:
void _parse(String);
};
URL::URL() :
protocol(),
host(),
path(),
port(0)
{}
URL(const String& string) {
_parse(string);
}
URL::URL(const String& string) {
_parse(string);
}
String protocol;
String host;
String path;
uint16_t port;
void URL::_parse(String buffer) {
private:
// cut the protocol part
int index = buffer.indexOf("://");
if (index > 0) {
this->protocol = buffer.substring(0, index);
buffer.remove(0, (index + 3));
}
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;
}
if (this->protocol == "http") {
this->port = 80;
} else if (this->protocol == "https") {
this->port = 443;
}
// cut the host part
String _host;
// cut the host part
String _host;
index = buffer.indexOf('/');
if (index >= 0) {
_host = buffer.substring(0, index);
} else {
_host = buffer;
}
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 = "/";
}
// 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;
// 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;
}
}
}
};

code/espurna/light.ino → code/espurna/light.cpp View File

@ -6,14 +6,17 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#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 <Ticker.h>
@ -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.h>
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,187 +850,93 @@ void lightBroker() {
// API
// -----------------------------------------------------------------------------
size_t lightChannels() {
return _light_channels.size();
}
bool lightHasColor() {
return _light_has_color;
}
bool lightUseCCT() {
return _light_use_cct;
}
void _lightComms(const unsigned char mask) {
// Report color and brightness to MQTT broker
#if MQTT_SUPPORT
if (mask & Light::COMMS_NORMAL) lightMQTT();
if (mask & Light::COMMS_GROUP) lightMQTTGroup();
#endif
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsPost(_lightWebSocketStatus);
#endif
// Report channels to local broker
#if BROKER_SUPPORT
lightBroker();
#endif
}
void lightUpdate(bool save, bool forward, bool group_forward) {
// Calculate values based on inputs and brightness
_light_brightness_func();
// Only update if a channel has changed
if (!_light_dirty) return;
_light_dirty = false;
// Update channels
for (unsigned int i=0; i < _light_channels.size(); i++) {
_light_channels[i].target = _light_state && _light_channels[i].state ? _light_channels[i].value : 0;
//DEBUG_MSG_P("[LIGHT] Channel #%u target value: %u\n", i, _light_channels[i].target);
}
// Channel transition will be handled by the provider function
// User can configure total transition time, step time is a fixed value
const unsigned long steps = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1;
_light_transition_ticker.once_ms(LIGHT_TRANSITION_STEP, _lightProviderScheduleUpdate, steps);
// Delay every communication 100ms to avoid jamming
const unsigned char mask =
((forward) ? Light::COMMS_NORMAL : Light::COMMS_NONE) |
((group_forward) ? Light::COMMS_GROUP : Light::COMMS_NONE);
_light_comms_ticker.once_ms(LIGHT_COMMS_DELAY, _lightComms, mask);
_lightSaveRtcmem();
#if LIGHT_SAVE_ENABLED
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
if (save) _light_save_ticker.once(LIGHT_SAVE_DELAY, _lightSaveSettings);
#endif
};
#if API_SUPPORT
void lightUpdate(bool save, bool forward) {
lightUpdate(save, forward, true);
}
void _lightAPISetup() {
#if LIGHT_SAVE_ENABLED == 0
void lightSave() {
_lightSaveSettings();
}
#endif
if (_light_has_color) {
void lightState(unsigned char id, bool state) {
if (id >= _light_channels.size()) return;
if (_light_channels[id].state != state) {
_light_channels[id].state = state;
_light_dirty = true;
}
}
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);
}
);
bool lightState(unsigned char id) {
if (id >= _light_channels.size()) return false;
return _light_channels[id].state;
}
apiRegister(MQTT_TOPIC_COLOR_HSV,
[](char * buffer, size_t len) {
_toHSV(buffer, len);
},
[](const char * payload) {
lightColor(payload, false);
lightUpdate(true, true);
}
);
void lightState(bool state) {
if (_light_state != state) {
_light_state = state;
_light_dirty = true;
}
}
apiRegister(MQTT_TOPIC_KELVIN,
[](char * buffer, size_t len) {},
[](const char * payload) {
_lightAdjustKelvin(payload);
lightUpdate(true, true);
}
);
bool lightState() {
return _light_state;
}
apiRegister(MQTT_TOPIC_MIRED,
[](char * buffer, size_t len) {},
[](const char * payload) {
_lightAdjustMireds(payload);
lightUpdate(true, true);
}
);
void lightColor(const char * color, bool rgb) {
DEBUG_MSG_P(PSTR("[LIGHT] %s: %s\n"), rgb ? "RGB" : "HSV", color);
if (rgb) {
_fromRGB(color);
} else {
_fromHSV(color);
}
}
void lightColor(const char * color) {
lightColor(color, true);
}
for (unsigned int id=0; id<_light_channels.size(); id++) {
void lightColor(unsigned long color) {
_fromLong(color, false);
}
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);
}
);
String lightColor(bool rgb) {
char str[12];
if (rgb) {
_toRGB(str, sizeof(str));
} else {
_toHSV(str, sizeof(str));
}
return String(str);
}
String lightColor() {
return lightColor(true);
}
long lightChannel(unsigned char id) {
if (id >= _light_channels.size()) return 0;
return _light_channels[id].inputValue;
}
void lightChannel(unsigned char id, long value) {
if (id >= _light_channels.size()) return;
_setInputValue(id, constrain(value, Light::VALUE_MIN, Light::VALUE_MAX));
}
void lightChannelStep(unsigned char id, long steps, long multiplier) {
lightChannel(id, static_cast<int>(lightChannel(id)) + (steps * multiplier));
}
long lightBrightness() {
return _light_brightness;
}
apiRegister(MQTT_TOPIC_TRANSITION,
[](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), lightTransitionTime());
},
[](const char * payload) {
lightTransitionTime(atol(payload));
}
);
void lightBrightness(long brightness) {
_light_brightness = constrain(brightness, Light::BRIGHTNESS_MIN, Light::BRIGHTNESS_MAX);
}
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);
}
);
void lightBrightnessStep(long steps, long multiplier) {
lightBrightness(static_cast<int>(_light_brightness) + (steps * multiplier));
}
unsigned int lightTransitionTime() {
if (_light_use_transitions) {
return _light_transition_time;
} else {
return 0;
}
}
#endif // API_SUPPORT
void lightTransitionTime(unsigned long m) {
if (0 == m) {
_light_use_transitions = false;
} else {
_light_use_transitions = true;
_light_transition_time = m;
}
setSetting("useTransitions", _light_use_transitions);
setSetting("lightTime", _light_transition_time);
saveSettings();
}
// -----------------------------------------------------------------------------
// SETUP
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
@ -1099,93 +1026,6 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
#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) {
@ -1259,6 +1099,188 @@ void _lightInitCommands() {
#endif // TERMINAL_SUPPORT
size_t lightChannels() {
return _light_channels.size();
}
bool lightHasColor() {
return _light_has_color;
}
bool lightUseCCT() {
return _light_use_cct;
}
void _lightComms(unsigned char mask) {
// Report color and brightness to MQTT broker
#if MQTT_SUPPORT
if (mask & Light::COMMS_NORMAL) lightMQTT();
if (mask & Light::COMMS_GROUP) lightMQTTGroup();
#endif
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsPost(_lightWebSocketStatus);
#endif
// Report channels to local broker
#if BROKER_SUPPORT
lightBroker();
#endif
}
void lightUpdate(bool save, bool forward, bool group_forward) {
// Calculate values based on inputs and brightness
_light_brightness_func();
// Only update if a channel has changed
if (!_light_dirty) return;
_light_dirty = false;
// Update channels
for (unsigned int i=0; i < _light_channels.size(); i++) {
_light_channels[i].target = _light_state && _light_channels[i].state ? _light_channels[i].value : 0;
//DEBUG_MSG_P("[LIGHT] Channel #%u target value: %u\n", i, _light_channels[i].target);
}
// Channel transition will be handled by the provider function
// User can configure total transition time, step time is a fixed value
const unsigned long steps = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1;
_light_transition_ticker.once_ms(LIGHT_TRANSITION_STEP, _lightProviderScheduleUpdate, steps);
// Delay every communication 100ms to avoid jamming
const unsigned char mask =
((forward) ? Light::COMMS_NORMAL : Light::COMMS_NONE) |
((group_forward) ? Light::COMMS_GROUP : Light::COMMS_NONE);
_light_comms_ticker.once_ms(LIGHT_COMMS_DELAY, _lightComms, mask);
_lightSaveRtcmem();
#if LIGHT_SAVE_ENABLED
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
if (save) _light_save_ticker.once(LIGHT_SAVE_DELAY, _lightSaveSettings);
#endif
};
void lightUpdate(bool save, bool forward) {
lightUpdate(save, forward, true);
}
#if LIGHT_SAVE_ENABLED == 0
void lightSave() {
_lightSaveSettings();
}
#endif
void lightState(unsigned char id, bool state) {
if (id >= _light_channels.size()) return;
if (_light_channels[id].state != state) {
_light_channels[id].state = state;
_light_dirty = true;
}
}
bool lightState(unsigned char id) {
if (id >= _light_channels.size()) return false;
return _light_channels[id].state;
}
void lightState(bool state) {
if (_light_state != state) {
_light_state = state;
_light_dirty = true;
}
}
bool lightState() {
return _light_state;
}
void lightColor(const char * color, bool rgb) {
DEBUG_MSG_P(PSTR("[LIGHT] %s: %s\n"), rgb ? "RGB" : "HSV", color);
if (rgb) {
_fromRGB(color);
} else {
_fromHSV(color);
}
}
void lightColor(const char * color) {
lightColor(color, true);
}
void lightColor(unsigned long color) {
_fromLong(color, false);
}
String lightColor(bool rgb) {
char str[12];
if (rgb) {
_toRGB(str, sizeof(str));
} else {
_toHSV(str, sizeof(str));
}
return String(str);
}
String lightColor() {
return lightColor(true);
}
long lightChannel(unsigned char id) {
if (id >= _light_channels.size()) return 0;
return _light_channels[id].inputValue;
}
void lightChannel(unsigned char id, long value) {
if (id >= _light_channels.size()) return;
_setInputValue(id, constrain(value, Light::VALUE_MIN, Light::VALUE_MAX));
}
void lightChannelStep(unsigned char id, long steps, long multiplier) {
lightChannel(id, static_cast<int>(lightChannel(id)) + (steps * multiplier));
}
long lightBrightness() {
return _light_brightness;
}
void lightBrightness(long brightness) {
_light_brightness = constrain(brightness, Light::BRIGHTNESS_MIN, Light::BRIGHTNESS_MAX);
}
void lightBrightnessStep(long steps, long multiplier) {
lightBrightness(static_cast<int>(_light_brightness) + (steps * multiplier));
}
unsigned int lightTransitionTime() {
if (_light_use_transitions) {
return _light_transition_time;
} else {
return 0;
}
}
void lightTransitionTime(unsigned long ms) {
if (0 == ms) {
_light_use_transitions = false;
} else {
_light_use_transitions = true;
_light_transition_time = ms;
}
setSetting("useTransitions", _light_use_transitions);
setSetting("lightTime", _light_transition_time);
saveSettings();
}
// -----------------------------------------------------------------------------
// SETUP
// -----------------------------------------------------------------------------
#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);

+ 30
- 23
code/espurna/light.h View File

@ -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();

+ 2
- 0
code/espurna/light_config.h View File

@ -6,6 +6,8 @@ LIGHT MODULE
#pragma once
#include "espurna.h"
constexpr const unsigned char _lightEnablePin() {
return LIGHT_ENABLE_PIN;
}


code/espurna/lightfox.ino → code/espurna/lightfox.cpp View File


code/espurna/llmnr.ino → code/espurna/llmnr.cpp View File

@ -6,9 +6,9 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if LLMNR_SUPPORT
#include "llmnr.h"
#include <ESP8266LLMNR.h>
#if LLMNR_SUPPORT
void llmnrSetup() {
LLMNR.begin(getSetting("hostname").c_str());

+ 16
- 0
code/espurna/llmnr.h View File

@ -0,0 +1,16 @@
/*
LLMNR MODULE
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "espurna.h"
#if LLMNR_SUPPORT
#include <ESP8266LLMNR.h>
void llmnrSetup();
#endif // LLMNR_SUPPORT

+ 315
- 0
code/espurna/main.cpp View File

@ -0,0 +1,315 @@
/*
ESPurna
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
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 <http://www.gnu.org/licenses/>.
*/
#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<void_callback_f> _loop_callbacks;
std::vector<void_callback_f> _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<int>("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);
}

code/espurna/mdns.ino → code/espurna/mdns.cpp View File

@ -10,6 +10,11 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// mDNS Server
// -----------------------------------------------------------------------------
#include "mdns.h"
#include "mqtt.h"
#include "utils.h"
#if MDNS_SERVER_SUPPORT
#include <ESP8266mDNS.h>

+ 11
- 0
code/espurna/mdns.h View File

@ -0,0 +1,11 @@
#pragma once
#include "espurna.h"
#include <Arduino.h>
#if MDNS_SERVER_SUPPORT
#include <ESP8266mDNS.h>
void mdnsServerSetup();
#endif

code/espurna/migrate.ino → code/espurna/migrate.cpp View File


code/espurna/mqtt.ino → code/espurna/mqtt.cpp View File

@ -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 <vector>
#include <utility>
#include <Ticker.h>
#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

+ 27
- 4
code/espurna/mqtt.h View File

@ -9,6 +9,8 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot
#pragma once
#include "espurna.h"
#include <WString.h>
#include <utility>
@ -17,9 +19,6 @@ Updated secure client support by Niek van der Maas < mail at niekvandermaas dot
using mqtt_callback_f = std::function<void(unsigned int type, const char * topic, char * payload)>;
using mqtt_msg_t = std::pair<String, String>; // 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

code/espurna/netbios.ino → code/espurna/netbios.cpp View File

@ -6,9 +6,9 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if NETBIOS_SUPPORT
#include "netbios.h"
#include <ESP8266NetBIOS.h>
#if NETBIOS_SUPPORT
void netbiosSetup() {
static WiFiEventHandler _netbios_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {

+ 17
- 0
code/espurna/netbios.h View File

@ -0,0 +1,17 @@
/*
NETBIOS MODULE
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "espurna.h"
#if NETBIOS_SUPPORT
#include <ESP8266NetBIOS.h>
void netbiosSetup();
#endif // NETBIOS_SUPPORT

code/espurna/nofuss.ino → code/espurna/nofuss.cpp View File

@ -6,9 +6,13 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#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

+ 17
- 0
code/espurna/nofuss.h View File

@ -0,0 +1,17 @@
/*
NOFUSS MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "espurna.h"
#if NOFUSS_SUPPORT
#include <NoFUSSClient.h>
void nofussSetup();
#endif // NOFUSS_SUPPORT

code/espurna/ntp.ino → code/espurna/ntp.cpp View File

@ -11,6 +11,8 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include "ntp.h"
#if NTP_SUPPORT && !NTP_LEGACY_SUPPORT
#include <Arduino.h>
@ -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() {

+ 9
- 5
code/espurna/ntp.h View File

@ -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 <TimeLib.h>
#include "libs/NtpClientWrap.h"
#include <WiFiUdp.h>
#include <NtpClientLib.h>
time_t ntpLocal2UTC(time_t local);
#else // POSIX time functions + configTime(...)
@ -44,8 +46,10 @@ struct NtpCalendarWeekday {
using NtpBroker = TBroker<TBrokerType::Datetime, const NtpTick, time_t, const String&>;
String ntpDateTime(tm* timestruct);
String ntpDateTime(time_t ts);
String ntpDateTime();
bool ntpSynced();
void ntpSetup();


code/espurna/ntp_legacy.ino → code/espurna/ntp_legacy.cpp View File

@ -6,13 +6,15 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "ntp.h"
#if NTP_LEGACY_SUPPORT && NTP_SUPPORT
#include <Ticker.h>
#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;

code/espurna/ota.ino → code/espurna/ota.cpp View File


+ 3
- 1
code/espurna/ota.h View File

@ -6,8 +6,10 @@ OTA MODULE
#pragma once
#include <Updater.h>
#include "espurna.h"
#include <ArduinoOTA.h>
#include <Updater.h>
#if OTA_WEB_SUPPORT


code/espurna/ota_arduinoota.ino → code/espurna/ota_arduinoota.cpp View File

@ -6,9 +6,10 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "ota.h"
#if OTA_ARDUINOOTA_SUPPORT
#include "ota.h"
#include "system.h"
#include "ws.h"

code/espurna/ota_asynctcp.ino → code/espurna/ota_asynctcp.cpp View File

@ -6,20 +6,25 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "ota.h"
#if OTA_CLIENT == OTA_CLIENT_ASYNCTCP
// -----------------------------------------------------------------------------
// Terminal and MQTT OTA command handlers
// -----------------------------------------------------------------------------
#include <Arduino.h>
#include "espurna.h"
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
#include <Schedule.h>
#include <ESPAsyncTCP.h>
#include "mqtt.h"
#include "ota.h"
#include "system.h"
#include "settings.h"
#include "terminal.h"
#include "libs/URL.h"

code/espurna/ota_httpupdate.ino → code/espurna/ota_httpupdate.cpp View File

@ -10,12 +10,13 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
// OTA by using Core's HTTP(s) updater
// -----------------------------------------------------------------------------
#include "ota.h"
#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE
#include <memory>
#include "mqtt.h"
#include "ota.h"
#include "system.h"
#include "terminal.h"

code/espurna/ota_web.ino → code/espurna/ota_web.cpp View File

@ -8,14 +8,12 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#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);

code/espurna/relay.ino → code/espurna/relay.cpp View File

@ -6,6 +6,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "relay.h"
#if RELAY_SUPPORT
#include <Ticker.h>
@ -14,13 +16,17 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include <functional>
#include <bitset>
#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<RELAYS_MAX>& 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<RELAYS_MAX>& 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
}

+ 11
- 34
code/espurna/relay.h View File

@ -8,42 +8,12 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include <bitset>
#include "espurna.h"
#include "rpc.h"
#include "utils.h"
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<RELAYS_MAX> 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;
#include <bitset>
};
constexpr size_t RELAYS_MAX = 32;
PayloadStatus relayParsePayload(const char * payload);
@ -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();

+ 2
- 0
code/espurna/relay_config.h View File

@ -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 :


code/espurna/rfbridge.ino → code/espurna/rfbridge.cpp View File

@ -6,12 +6,14 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "rfbridge.h"
#if RF_SUPPORT
#include <queue>
#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_t> _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<n; p++) {
sprintf_P(&out[p*2], PSTR("%02X"), in[p]);
}
@ -94,7 +94,7 @@ void _rfbWebSocketSendCodeArray(JsonObject& root, unsigned char start, unsigned
JsonArray& on = rfb.createNestedArray("on");
JsonArray& off = rfb.createNestedArray("off");
for (byte id=start; id<start+size; id++) {
for (uint8_t id=start; id<start+size; id++) {
on.add(rfbRetrieve(id, true));
off.add(rfbRetrieve(id, false));
}
@ -130,10 +130,8 @@ void _rfbWebSocketOnData(JsonObject& root) {
#endif // WEB_SUPPORT
/*
From an hexa char array ("A220EE...") to a byte array (half the size)
*/
static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE * 2) {
// From an hexa char array ("A220EE...") to a byte array (half the size)
static int _rfbToArray(const char * in, uint8_t * out, int length = RF_MESSAGE_SIZE * 2) {
int n = strlen(in);
if (n > 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<n; j++) {
Serial.write(message[j]);
}
}
void _rfbSend() {
if (!_rfb_transmit) return;
// Check if there is something in the queue
if (_rfb_message_queue.empty()) return;
static unsigned long last = 0;
if (millis() - last < RF_SEND_DELAY) return;
last = millis();
// Pop the first message and send it
rfb_message_t message = _rfb_message_queue.front();
_rfb_message_queue.pop();
_rfbSend(message.code);
// Push it to the stack again if we need to send it more than once
if (message.times > 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<n; j++) {
Serial.write(message[j]);
}
}
void _rfbSend(uint8_t * 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 _rfbSend() {
if (!_rfb_transmit) return;
// Check if there is something in the queue
if (_rfb_message_queue.empty()) return;
static unsigned long last = 0;
if (millis() - last < RF_SEND_DELAY) return;
last = millis();
// Pop the first message and send it
rfb_message_t message = _rfb_message_queue.front();
_rfb_message_queue.pop();
_rfbSend(message.code);
// Push it to the stack again if we need to send it more than once
if (message.times > 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

+ 2
- 0
code/espurna/rfbridge.h View File

@ -6,6 +6,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "espurna.h"
#if RF_SUPPORT
#if RFB_DIRECT


code/espurna/rfm69.ino → code/espurna/rfm69.cpp View File

@ -6,10 +6,11 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#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 <xose dot perez at gmail dot com>
// 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<RFM69_MAX_NODES; i++) {
_rfm69_node_info[i].duplicates = 0;
_rfm69_node_info[i].missing = 0;
_rfm69_node_info[i].count = 0;
}
_rfm69_node_count = 0;
_rfm69_packet_count = 0;
}
// -----------------------------------------------------------------------------
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);
}
};
RFM69Wrap * _rfm69_radio;
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
@ -238,16 +299,6 @@ void _rfm69Loop() {
}
void _rfm69Clear() {
for(unsigned int i=0; i<RFM69_MAX_NODES; i++) {
_rfm69_node_info[i].duplicates = 0;
_rfm69_node_info[i].missing = 0;
_rfm69_node_info[i].count = 0;
}
_rfm69_node_count = 0;
_rfm69_packet_count = 0;
}
// -----------------------------------------------------------------------------
// RFM69
// -----------------------------------------------------------------------------

+ 4
- 12
code/espurna/rfm69.h View File

@ -8,21 +8,13 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#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 <RFM69.h>
#include <RFM69_ATC.h>
#include <SPI.h>
void rfm69Setup();


code/espurna/rpc.ino → code/espurna/rpc.cpp View File


+ 2
- 0
code/espurna/rpc.h View File

@ -6,6 +6,8 @@ Part of MQTT and API modules
#pragma once
#include "espurna.h"
#include <vector>
#include <utility>


code/espurna/rpnrules.ino → code/espurna/rpnrules.cpp View File

@ -6,11 +6,16 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#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;

+ 1
- 2
code/espurna/rpnrules.h View File

@ -8,8 +8,7 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
// TODO: need this prototype for .ino
struct rpn_context;
#include "espurna.h"
#if RPN_RULES_SUPPORT


code/espurna/rtcmem.ino → code/espurna/rtcmem.cpp View File

@ -6,6 +6,10 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include "rtcmem.h"
volatile RtcmemData* Rtcmem = reinterpret_cast<volatile RtcmemData*>(RTCMEM_ADDR);
bool _rtcmem_status = false;
void _rtcmemErase() {

+ 7
- 1
code/espurna/rtcmem.h View File

@ -8,6 +8,11 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#pragma once
#include <Arduino.h>
#include <cstdint>
#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<volatile RtcmemData*>(RTCMEM_ADDR);
extern volatile RtcmemData* Rtcmem;
bool rtcmemStatus();
void rtcmemSetup();

code/espurna/scheduler.ino → code/espurna/scheduler.cpp View File

@ -7,11 +7,15 @@ Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#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;

+ 14
- 0
code/espurna/scheduler.h View File

@ -0,0 +1,14 @@
/*
SCHEDULER MODULE
Copyright (C) 2017 by faina09
Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#pragma once
#include "espurna.h"
void schSetup();

code/espurna/sensor.ino → code/espurna/sensor.cpp View File

@ -6,20 +6,202 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "sensor.h"
#if SENSOR_SUPPORT
#include <vector>
#include <float.h>
#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<unsigned char> _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<BaseEmonSensor*>(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<BaseEmonSensor*>(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 `<kwh>+<ws>`, value without `+` is treated as `<ws>`
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<BaseSensor *> _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<unsigned char> _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<typename T> 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 <prefix>Magnitudes
const String ws_name = String(prefix) + "Magnitudes";
const String ws_name = prefix + "Magnitudes";
// config uses <prefix>Magnitude<index> (cut 's')
const String conf_name = ws_name.substring(0, ws_name.length() - 1);
@ -560,33 +837,17 @@ template<typename T> 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<magnitudeCount(); ++i) {
//name.add(magnitudeName(i));
type.add(magnitudeType(i));
index.add(magnitudeIndex(i));
idx.add(getSetting({conf_name, i}, 0));
}
}
/*
template<typename T> void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
// ws produces flat list <prefix>Magnitudes
const String ws_name = String(prefix) + "Magnitudes";
// config uses <prefix>Magnitude<index> (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<uint8_t>(magnitude.global);
type.add<uint8_t>(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<BaseEmonSensor*>(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<BaseEmonSensor*>(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 `<kwh>+<ws>`, value without `+` is treated as `<ws>`
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

+ 3
- 177
code/espurna/sensor.h View File

@ -9,7 +9,7 @@ Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#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
//--------------------------------------------------------------------------------

+ 1
- 0
code/espurna/sensors/I2CSensor.h View File

@ -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?


code/espurna/settings.ino → code/espurna/settings.cpp View File

@ -6,17 +6,15 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <vector>
#include "settings.h"
#include <ArduinoJson.h>
#include <vector>
#include "storage_eeprom.h"
#include "settings_internal.h"
#include "settings.h"
#include <cstdlib>
// -----------------------------------------------------------------------------
// 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<int>(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<unsigned long>(value);
}
template <>
unsigned short convert(const String& value) {
return convert<unsigned long>(value);
}
template <>
unsigned char convert(const String& value) {
return convert<unsigned long>(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<String> _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<String> settingsKeys() {
// Get sorted list of keys
std::vector<String> keys;
//unsigned int size = settingsKeyCount();
unsigned int size = settingsKeyCount();
auto size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
//String key = settingsKeyName(i);
@ -141,6 +283,7 @@ void moveSettings(const String& from, const String& to) {
}
}
#if 0
template<typename R, settings::internal::convert_t<R> 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<typename T>
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<typename T>
String getSetting(const String& key, unsigned char index, T defaultValue) {
return getSetting({key, index}, defaultValue);
}
template<typename T>
bool setSetting(const String& key, unsigned char index, T value) {
return setSetting({key, index}, value);
}
template<typename T>
bool hasSetting(const String& key, unsigned char index) {
return hasSetting({key, index});
}
template<typename T>
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<String> keys = _settingsKeys();
auto keys = settingsKeys();
// Add the key-values to the json object
for (unsigned int i=0; i<keys.size(); i++) {

+ 107
- 4
code/espurna/settings.h View File

@ -8,13 +8,15 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include <Arduino.h>
#include <functional>
#include <utility>
#include <vector>
#include <ArduinoJson.h>
#include "espurna.h"
#include "libs/EmbedisWrap.h"
#include "settings_internal.h"
// --------------------------------------------------------------------------
@ -63,17 +65,83 @@ using settings_cfg_list_t = std::initializer_list<settings_cfg_t>;
// --------------------------------------------------------------------------
namespace settings {
namespace internal {
uint32_t u32fromString(const String& string, int base);
template <typename T>
using convert_t = T(*)(const String& value);
template <typename T>
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<typename R, settings::internal::convert_t<R> Rfunc = settings::internal::convert>
R getSetting(const settings_key_t& key, R defaultValue) __attribute__((noinline));
#endif
template<typename R, settings::internal::convert_t<R> Rfunc = settings::internal::convert>
R getSetting(const settings_key_t& key, R defaultValue);
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<typename T>
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<String> settingsKeys();
void settingsProcessConfig(const settings_cfg_list_t& config, settings_filter_t filter = nullptr);
// --------------------------------------------------------------------------
unsigned long settingsSize();
void migrate();
void settingsSetup();
// -----------------------------------------------------------------------------
// Deprecated implementation
// -----------------------------------------------------------------------------
template <typename T>
String getSetting(const String& key, unsigned char index, T defaultValue)
@ -103,3 +183,26 @@ __attribute__((deprecated("hasSetting({key, index}) should be used instead")));
template<typename T>
bool delSetting(const String& key, unsigned char index)
__attribute__((deprecated("delSetting({key, index}) should be used instead")));
// --------------------------------------------------------------------------
template<typename T>
String getSetting(const String& key, unsigned char index, T defaultValue) {
return getSetting({key, index}, defaultValue);
}
template<typename T>
bool setSetting(const String& key, unsigned char index, T value) {
return setSetting({key, index}, value);
}
template<typename T>
bool hasSetting(const String& key, unsigned char index) {
return hasSetting({key, index});
}
template<typename T>
bool delSetting(const String& key, unsigned char index) {
return delSetting({key, index});
}

+ 1
- 65
code/espurna/settings_internal.h View File

@ -6,70 +6,6 @@ SETTINGS MODULE
#pragma once
#include <Arduino.h>
#include <cstdlib>
// --------------------------------------------------------------------------
namespace settings {
namespace internal {
template <typename T>
using convert_t = T(*)(const String& value);
template <typename T>
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<int>(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<unsigned long>(value);
}
template <>
unsigned short convert(const String& value) {
return convert<unsigned long>(value);
}
template <>
unsigned char convert(const String& value) {
return convert<unsigned long>(value);
}
template <>
String convert(const String& value) {
return value;
}
} // namespace settings::internal
} // namespace settings

code/espurna/ssdp.ino → code/espurna/ssdp.cpp View File

@ -8,10 +8,13 @@ https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604
*/
#include "ssdp.h"
#if SSDP_SUPPORT
#include <ESP8266SSDP.h>
#include "web.h"
#include "utils.h"
const char _ssdp_template[] PROGMEM =

+ 20
- 0
code/espurna/ssdp.h View File

@ -0,0 +1,20 @@
/*
SSDP MODULE
Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
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 <ESP8266SSDP.h>
void ssdpSetup();
#endif // SSDP_SUPPORT

code/espurna/storage_eeprom.ino → code/espurna/storage_eeprom.cpp View File

@ -4,8 +4,8 @@ EEPROM MODULE
*/
#include "debug.h"
#include "storage_eeprom.h"
#include "settings.h"
EEPROM_Rotate EEPROMr;
bool _eeprom_commit = false;

+ 2
- 0
code/espurna/storage_eeprom.h View File

@ -11,6 +11,8 @@ EEPROM MODULE
#include <EEPROM_Rotate.h>
#include "espurna.h"
extern EEPROM_Rotate EEPROMr;
void eepromSectorsDebug();


code/espurna/system.ino → code/espurna/system.cpp View File

@ -6,10 +6,16 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include "system.h"
#include <Ticker.h>
#include <EEPROM_Rotate.h>
#include <Schedule.h>
#include "system.h"
#include <cstdint>
#include "rtcmem.h"
#include "ws.h"
#include "ntp.h"
// -----------------------------------------------------------------------------

+ 6
- 4
code/espurna/system.h View File

@ -8,10 +8,7 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include <Ticker.h>
#include <Schedule.h>
#include <cstdint>
#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();

code/espurna/telnet.ino → code/espurna/telnet.cpp View File

@ -15,11 +15,14 @@ Updated to use WiFiServer and support reverse connections by Niek van der Maas <
*/
#include "telnet.h"
#if TELNET_SUPPORT
#include <memory>
#include "board.h"
#include "telnet.h"
#include "ws.h"
TTelnetServer _telnetServer(TELNET_PORT);
std::unique_ptr<TTelnetClient> _telnetClients[TELNET_MAX_CLIENTS];

+ 23
- 9
code/espurna/telnet.h View File

@ -8,12 +8,16 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#pragma once
#include "espurna.h"
#include <Arduino.h>
#include <Schedule.h>
#include <memory>
#include <list>
#include <Schedule.h>
#if TELNET_SERVER == TELNET_SERVER_ASYNC
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
struct AsyncBufferedClient {
@ -44,18 +48,28 @@ struct AsyncBufferedClient {
std::list<buffer_t> _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();

code/espurna/terminal.ino → code/espurna/terminal.cpp View File

@ -6,14 +6,20 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
// (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 <vector>
#include <Stream.h>
@ -66,7 +72,7 @@ void _terminalHelpCommand() {
void _terminalKeysCommand() {
// Get sorted list of keys
std::vector<String> keys = _settingsKeys();
auto keys = settingsKeys();
// Write key-values
DEBUG_MSG_P(PSTR("Current settings:\n"));

+ 3
- 0
code/espurna/terminal.h View File

@ -8,6 +8,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#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();


code/espurna/thermostat.ino → code/espurna/thermostat.cpp View File

@ -6,19 +6,16 @@ Copyright (C) 2017 by Dmitry Blinov <dblinov76 at gmail dot com>
*/
#if THERMOSTAT_SUPPORT
#include "thermostat.h"
#include <float.h>
#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

+ 47
- 0
code/espurna/thermostat.h View File

@ -8,6 +8,8 @@ Copyright (C) 2017 by Dmitry Blinov <dblinov76 at gmail dot com>
#pragma once
#include "espurna.h"
#include <ArduinoJson.h>
#include <float.h>
@ -15,5 +17,50 @@ Copyright (C) 2017 by Dmitry Blinov <dblinov76 at gmail dot com>
#include <SSD1306.h> // 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(bool state)>;
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();

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save