mirror of
https://github.com/xoseperez/espurna.git
synced 2026-03-04 23:44:20 +01:00
- gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
414 lines
10 KiB
C++
414 lines
10 KiB
C++
/*
|
|
|
|
DEBUG MODULE
|
|
|
|
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
|
|
|
|
*/
|
|
|
|
#include "espurna.h"
|
|
|
|
#if DEBUG_SUPPORT
|
|
|
|
#include <limits>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
#include "settings.h"
|
|
#include "telnet.h"
|
|
#include "web.h"
|
|
#include "ws.h"
|
|
|
|
#if DEBUG_UDP_SUPPORT
|
|
#include <WiFiUdp.h>
|
|
WiFiUDP _udp_debug;
|
|
|
|
constexpr bool _udp_syslog_enabled = (514 == DEBUG_UDP_PORT);
|
|
char _udp_syslog_header[64];
|
|
#endif
|
|
|
|
bool _debug_enabled = false;
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// printf-like debug methods
|
|
// -----------------------------------------------------------------------------
|
|
|
|
constexpr int DEBUG_SEND_STRING_BUFFER_SIZE = 128;
|
|
|
|
void _debugSendInternal(const char * message, bool add_timestamp = DEBUG_ADD_TIMESTAMP);
|
|
|
|
// TODO: switch to newlib vsnprintf for latest Cores to support PROGMEM args
|
|
void _debugSend(const char * format, va_list args) {
|
|
|
|
char temp[DEBUG_SEND_STRING_BUFFER_SIZE];
|
|
int len = vsnprintf(temp, sizeof(temp), format, args);
|
|
|
|
// strlen(...) + '\0' already in temp buffer, avoid using malloc when possible
|
|
if (len < DEBUG_SEND_STRING_BUFFER_SIZE) {
|
|
_debugSendInternal(temp);
|
|
return;
|
|
}
|
|
|
|
len += 1;
|
|
auto* buffer = static_cast<char*>(malloc(len));
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
vsnprintf(buffer, len, format, args);
|
|
|
|
_debugSendInternal(buffer);
|
|
free(buffer);
|
|
|
|
}
|
|
|
|
void debugSendRaw(const char* line, bool timestamp) {
|
|
if (!_debug_enabled) return;
|
|
_debugSendInternal(line, timestamp);
|
|
}
|
|
|
|
void debugSend(const char* format, ...) {
|
|
|
|
if (!_debug_enabled) return;
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
_debugSend(format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
void debugSend_P(const char* format_P, ...) {
|
|
|
|
if (!_debug_enabled) return;
|
|
|
|
char format[strlen_P(format_P) + 1];
|
|
memcpy_P(format, format_P, sizeof(format));
|
|
|
|
va_list args;
|
|
va_start(args, format_P);
|
|
|
|
_debugSend(format, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// specific debug targets
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#if DEBUG_SERIAL_SUPPORT
|
|
void _debugSendSerial(const char* prefix, const char* data) {
|
|
if (prefix && (prefix[0] != '\0')) {
|
|
DEBUG_PORT.print(prefix);
|
|
}
|
|
DEBUG_PORT.print(data);
|
|
}
|
|
#endif // DEBUG_SERIAL_SUPPORT
|
|
|
|
#if DEBUG_LOG_BUFFER_SUPPORT
|
|
|
|
std::vector<char> _debug_log_buffer;
|
|
bool _debug_log_buffer_enabled = false;
|
|
|
|
void _debugLogBuffer(const char* prefix, const char* data) {
|
|
if (!_debug_log_buffer_enabled) return;
|
|
|
|
const auto prefix_len = strlen(prefix);
|
|
const auto data_len = strlen(data);
|
|
const auto total_len = prefix_len + data_len;
|
|
if (total_len >= std::numeric_limits<uint16_t>::max()) {
|
|
return;
|
|
}
|
|
if ((_debug_log_buffer.capacity() - _debug_log_buffer.size()) <= (total_len + 3)) {
|
|
_debug_log_buffer_enabled = false;
|
|
return;
|
|
}
|
|
|
|
_debug_log_buffer.push_back(total_len >> 8);
|
|
_debug_log_buffer.push_back(total_len & 0xff);
|
|
if (prefix && (prefix[0] != '\0')) {
|
|
_debug_log_buffer.insert(_debug_log_buffer.end(), prefix, prefix + prefix_len);
|
|
}
|
|
_debug_log_buffer.insert(_debug_log_buffer.end(), data, data + data_len);
|
|
}
|
|
|
|
void _debugLogBufferDump() {
|
|
size_t index = 0;
|
|
do {
|
|
if (index >= _debug_log_buffer.size()) {
|
|
break;
|
|
}
|
|
|
|
size_t len = _debug_log_buffer[index] << 8;
|
|
len = len | _debug_log_buffer[index + 1];
|
|
index += 2;
|
|
|
|
auto value = _debug_log_buffer[index + len];
|
|
_debug_log_buffer[index + len] = '\0';
|
|
_debugSendInternal(_debug_log_buffer.data() + index, false);
|
|
_debug_log_buffer[index + len] = value;
|
|
|
|
index += len;
|
|
} while (true);
|
|
|
|
_debug_log_buffer.clear();
|
|
_debug_log_buffer.shrink_to_fit();
|
|
}
|
|
|
|
bool debugLogBuffer() {
|
|
return _debug_log_buffer_enabled;
|
|
}
|
|
|
|
#endif // DEBUG_LOG_BUFFER_SUPPORT
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void _debugSendInternal(const char * message, bool add_timestamp) {
|
|
|
|
const size_t msg_len = strlen(message);
|
|
|
|
bool pause = false;
|
|
char timestamp[10] = {0};
|
|
|
|
#if DEBUG_ADD_TIMESTAMP
|
|
static bool continue_timestamp = true;
|
|
if (add_timestamp && continue_timestamp) {
|
|
snprintf(timestamp, sizeof(timestamp), "[%06lu] ", millis() % 1000000);
|
|
}
|
|
continue_timestamp = add_timestamp || (message[msg_len - 1] == 10) || (message[msg_len - 1] == 13);
|
|
#endif
|
|
|
|
#if DEBUG_SERIAL_SUPPORT
|
|
_debugSendSerial(timestamp, message);
|
|
#endif
|
|
|
|
#if DEBUG_UDP_SUPPORT
|
|
#if SYSTEM_CHECK_ENABLED
|
|
if (systemCheck()) {
|
|
#endif
|
|
_udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
|
|
if (_udp_syslog_enabled) {
|
|
_udp_debug.write(_udp_syslog_header);
|
|
}
|
|
_udp_debug.write(message);
|
|
pause = _udp_debug.endPacket() > 0;
|
|
#if SYSTEM_CHECK_ENABLED
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if DEBUG_TELNET_SUPPORT
|
|
pause = telnetDebugSend(timestamp, message) || pause;
|
|
#endif
|
|
|
|
#if DEBUG_WEB_SUPPORT
|
|
pause = wsDebugSend(timestamp, message) || pause;
|
|
#endif
|
|
|
|
#if DEBUG_LOG_BUFFER_SUPPORT
|
|
_debugLogBuffer(timestamp, message);
|
|
#endif
|
|
|
|
if (pause) {
|
|
optimistic_yield(1000);
|
|
}
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#if DEBUG_WEB_SUPPORT
|
|
|
|
void _debugWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
|
|
|
|
#if TERMINAL_SUPPORT
|
|
if (strcmp(action, "dbgcmd") == 0) {
|
|
if (!data.containsKey("command") || !data["command"].is<const char*>()) return;
|
|
const char* command = data["command"];
|
|
if (command && strlen(command)) {
|
|
auto command = data.get<const char*>("command");
|
|
terminalInject((void*) command, strlen(command));
|
|
terminalInject('\n');
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void debugWebSetup() {
|
|
|
|
wsRegister()
|
|
.onVisible([](JsonObject& root) { root["dbgVisible"] = 1; })
|
|
.onAction(_debugWebSocketOnAction);
|
|
|
|
}
|
|
|
|
#endif // DEBUG_WEB_SUPPOR
|
|
|
|
#if DEBUG_UDP_SUPPORT
|
|
|
|
// We use the syslog header as defined in RFC5424 (The Syslog Protocol), ref:
|
|
// - https://tools.ietf.org/html/rfc5424
|
|
// - https://github.com/xoseperez/espurna/issues/2312/
|
|
|
|
void debugUdpSyslogConfigure() {
|
|
|
|
snprintf_P(
|
|
_udp_syslog_header, sizeof(_udp_syslog_header),
|
|
PSTR("<%u>1 - %s ESPurna - - - "), DEBUG_UDP_FAC_PRI,
|
|
getSetting("hostname", getIdentifier()).c_str()
|
|
);
|
|
|
|
}
|
|
|
|
#endif // DEBUG_UDP_SUPPORT
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
void debugSetup() {
|
|
|
|
#if DEBUG_SERIAL_SUPPORT
|
|
DEBUG_PORT.begin(SERIAL_BAUDRATE);
|
|
#endif
|
|
|
|
#if DEBUG_UDP_SUPPORT
|
|
if (_udp_syslog_enabled) {
|
|
debugUdpSyslogConfigure();
|
|
espurnaRegisterReload(debugUdpSyslogConfigure);
|
|
}
|
|
#endif
|
|
|
|
#if TERMINAL_SUPPORT
|
|
|
|
#if DEBUG_LOG_BUFFER_SUPPORT
|
|
|
|
terminalRegisterCommand(F("DEBUG.BUFFER"), [](const terminal::CommandContext&) {
|
|
_debug_log_buffer_enabled = false;
|
|
if (!_debug_log_buffer.size()) {
|
|
DEBUG_MSG_P(PSTR("[DEBUG] Buffer is empty\n"));
|
|
return;
|
|
}
|
|
DEBUG_MSG_P(PSTR("[DEBUG] Buffer size: %u / %u bytes\n"),
|
|
_debug_log_buffer.size(),
|
|
_debug_log_buffer.capacity()
|
|
);
|
|
|
|
_debugLogBufferDump();
|
|
});
|
|
|
|
#endif // DEBUG_LOG_BUFFER_SUPPORT
|
|
|
|
#endif // TERMINAL_SUPPORT
|
|
|
|
|
|
}
|
|
|
|
namespace settings {
|
|
namespace internal {
|
|
|
|
template<>
|
|
String serialize(const DebugLogMode& value) {
|
|
String result;
|
|
switch (value) {
|
|
case DebugLogMode::Disabled:
|
|
result = "0";
|
|
break;
|
|
case DebugLogMode::SkipBoot:
|
|
result = "2";
|
|
break;
|
|
default:
|
|
case DebugLogMode::Enabled:
|
|
result = "1";
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template<>
|
|
DebugLogMode convert(const String& value) {
|
|
switch (value.toInt()) {
|
|
case 0:
|
|
return DebugLogMode::Disabled;
|
|
case 2:
|
|
return DebugLogMode::SkipBoot;
|
|
case 1:
|
|
default:
|
|
return DebugLogMode::Enabled;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void debugConfigureBoot() {
|
|
static_assert(
|
|
std::is_same<int, std::underlying_type<DebugLogMode>::type>::value,
|
|
"should be able to match DebugLogMode with int"
|
|
);
|
|
|
|
const auto mode = getSetting("dbgLogMode", DEBUG_LOG_MODE);
|
|
switch (mode) {
|
|
case DebugLogMode::SkipBoot:
|
|
schedule_function([]() {
|
|
_debug_enabled = true;
|
|
});
|
|
// fall through
|
|
case DebugLogMode::Disabled:
|
|
_debug_enabled = false;
|
|
break;
|
|
case DebugLogMode::Enabled:
|
|
_debug_enabled = true;
|
|
break;
|
|
}
|
|
|
|
debugConfigure();
|
|
}
|
|
|
|
void debugConfigure() {
|
|
|
|
// HardwareSerial::begin() will automatically enable this when
|
|
// `#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)`
|
|
// Core debugging also depends on various DEBUG_ESP_... being defined
|
|
{
|
|
#if defined(DEBUG_ESP_PORT)
|
|
#if not defined(NDEBUG)
|
|
constexpr bool debug_sdk = true;
|
|
#endif // !defined(NDEBUG)
|
|
#else
|
|
constexpr bool debug_sdk = false;
|
|
#endif // defined(DEBUG_ESP_PORT)
|
|
DEBUG_PORT.setDebugOutput(getSetting("dbgSDK", debug_sdk));
|
|
}
|
|
|
|
// Make sure other modules are aware of used GPIOs
|
|
#if DEBUG_SERIAL_SUPPORT
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wpragmas"
|
|
#pragma GCC diagnostic ignored "-Wtautological-compare"
|
|
if (&(DEBUG_PORT) == &Serial) {
|
|
gpioLock(1);
|
|
gpioLock(3);
|
|
} else if (&(DEBUG_PORT) == &Serial1) {
|
|
gpioLock(2);
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
#if DEBUG_LOG_BUFFER_SUPPORT
|
|
{
|
|
const auto enabled = getSetting("dbgLogBuf", 1 == DEBUG_LOG_BUFFER_ENABLED);
|
|
const auto size = getSetting("dbgLogBufSize", DEBUG_LOG_BUFFER_SIZE);
|
|
if (enabled) {
|
|
_debug_log_buffer_enabled = true;
|
|
_debug_log_buffer.reserve(size);
|
|
}
|
|
}
|
|
#endif // DEBUG_LOG_BUFFER
|
|
|
|
}
|
|
|
|
#endif // DEBUG_SUPPORT
|