Log: one line for whatever is dropped, a level the build lacks is reported, no false alarm for a 1.x handler
Audit of the paths that drop or refuse something. registerEvent() dropped an eleventh handler and a second one for the same name without a word: the name now gets the new handler, an eleventh name and a call without a name or a function print an error. A device that cannot be announced printed two lines, one without its id; it prints one. setLogLevel() above ALEX2ESP_LOG_MAX prints an error that names the build flag. "the handler sent no answer" is an error for a handler of onDirective() only: a 1.x handler may answer from a later loop(), its silence is a line at DEBUG. getDevice() returning nullptr and printMemoryInfo() are documented; keywords.txt has the 2.0 names and loses three that were removed. basicLight: static RAM 30,520 B, flash 333,989 B (+600); ALEX2ESP_LOG_MAX=0 30,496 / 327,953, =3 30,520 / 334,477; five examples, log0 and log3 build with 0 warnings. Host tests: 135 (dispatch 11 -> 16). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
77156ffbe5
commit
6e15226ce0
10 changed files with 216 additions and 26 deletions
|
|
@ -524,8 +524,7 @@ void Alex2ESP::publishDiscovery()
|
|||
else
|
||||
{
|
||||
// This object can never be sent: say so and announce the others
|
||||
logRefusal(discoverTopicSend.c_str(), result, length);
|
||||
ALEX2ESP_LOGE("device %s is not announced", device.getEndpointId().c_str());
|
||||
logUnannounced(device, length);
|
||||
}
|
||||
discoveryNext = device.nextDevice();
|
||||
}
|
||||
|
|
@ -613,6 +612,20 @@ AlexaSendResult Alex2ESP::trySend(const char *topic, JsonDocument &doc, size_t *
|
|||
return result;
|
||||
}
|
||||
|
||||
// One line for a device that is left out, with the device in it: the line of logRefusal() names the topic, which
|
||||
// is the same for every device
|
||||
void Alex2ESP::logUnannounced(const AlexaDevice &device, size_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
ALEX2ESP_LOGE("device %s not announced: no memory to build its discovery object (%u bytes of heap free)", device.getEndpointId().c_str(), (unsigned)ESP.getFreeHeap());
|
||||
}
|
||||
else
|
||||
{
|
||||
ALEX2ESP_LOGE("device %s not announced: its discovery object has %u bytes, the limit is %u (ALEX2ESP_MAX_MESSAGE)", device.getEndpointId().c_str(), (unsigned)length, (unsigned)ALEX2ESP_MAX_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
const char *Alex2ESP::logName(const char *topic) const
|
||||
{
|
||||
return AlexaBridgeLogic::topicForLog(topic, rootTopic.c_str());
|
||||
|
|
|
|||
|
|
@ -69,11 +69,14 @@ public:
|
|||
AsyncMqttClientDisconnectReason getDisconnectReason() const;
|
||||
|
||||
// Returns the device with this endpointId, creating it on first use. The pointer stays valid for the lifetime
|
||||
// of the client. nullptr when the heap has no room for another device; the reason is printed.
|
||||
// of the client. nullptr when the heap has no room for another device; the reason is printed. The examples
|
||||
// use the pointer unchecked: they create one device in setup(), where the heap is at its largest. A sketch
|
||||
// that creates devices by the dozen, or later than setup(), checks the pointer before it uses it.
|
||||
// Call it after begin(): a device takes the root topic of its reports when it is created.
|
||||
AlexaDevice *getDevice(const String &name, const String &endpointId);
|
||||
|
||||
// What the library prints on Serial: AlexaLogLevel::NONE, ERROR, INFO (the default) or DEBUG
|
||||
// What the library prints on Serial: AlexaLogLevel::NONE, ERROR, INFO (the default) or DEBUG. DEBUG in a
|
||||
// build without its lines (ALEX2ESP_LOG_MAX under 3) prints an error that names the build flag.
|
||||
void setLogLevel(AlexaLogLevel level);
|
||||
|
||||
// false before begin(): the sketch sets the clock itself (its own configTime() with a time zone, an RTC).
|
||||
|
|
@ -145,6 +148,7 @@ private:
|
|||
|
||||
AlexaSendResult trySend(const char *topic, JsonDocument &doc, size_t *length);
|
||||
void logRefusal(const char *topic, AlexaSendResult result, size_t length);
|
||||
void logUnannounced(const AlexaDevice &device, size_t length);
|
||||
const char *logName(const char *topic) const; // The topic as the log may print it: without the root topic
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -189,14 +189,23 @@ JsonDocument AlexaDevice::getDeviceJSON() const {
|
|||
|
||||
// Register an event callback function
|
||||
void AlexaDevice::registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)) {
|
||||
if (eventName == nullptr || callback == nullptr) {
|
||||
ALEX2ESP_LOGE("%s: handler not registered: registerEvent() takes the name of the event and a function",
|
||||
endpointId.c_str());
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < MAX_EVENTS; ++i) {
|
||||
// Find an empty slot for the new event
|
||||
if (eventNames[i] == nullptr) {
|
||||
eventNames[i] = eventName; // Store the event name
|
||||
eventCallbacks[i] = callback; // Store the callback function
|
||||
break;
|
||||
// A name that has a handler gets the new one: triggerEvent() calls the first it finds, so a second
|
||||
// entry with the name would never be called
|
||||
if (eventNames[i] == nullptr || strcmp(eventNames[i], eventName) == 0) {
|
||||
eventNames[i] = eventName;
|
||||
eventCallbacks[i] = callback;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ALEX2ESP_LOGE("%s: handler for %s not registered: the device has %d already (MAX_EVENTS); "
|
||||
"it calls ReportState, Event and DirectiveReceived",
|
||||
endpointId.c_str(), eventName, MAX_EVENTS);
|
||||
}
|
||||
|
||||
// Trigger the event and invoke the corresponding callback
|
||||
|
|
@ -256,10 +265,15 @@ void AlexaDevice::handleDirective(const JsonDocument& message) {
|
|||
ALEX2ESP_LOGE("%s: %s.%s refused as INVALID_DIRECTIVE: the device has no such capability, and its handler sent no answer",
|
||||
endpointId.c_str(), directive.ns, directive.name);
|
||||
reason = ERROR_NO_CAPABILITY;
|
||||
} else {
|
||||
ALEX2ESP_LOGE("%s: the handler sent no answer to %s.%s: Alexa will say that the device does not respond",
|
||||
} else if (directiveHandler != nullptr) {
|
||||
ALEX2ESP_LOGE("%s: the handler sent no answer to %s.%s, Alexa will report no response: send d.response() or d.error()",
|
||||
endpointId.c_str(), directive.ns, directive.name);
|
||||
return;
|
||||
} else {
|
||||
// A handler of 1.x has the whole directive and may answer it from a later loop(): no answer by now is
|
||||
// not an error
|
||||
ALEX2ESP_LOGD("%s: no answer to %s.%s from its handler yet", endpointId.c_str(), directive.ns, directive.name);
|
||||
return;
|
||||
}
|
||||
buildStatusMessage(directive.correlationToken, true).asErrorResponse(ERROR_INVALID_DIRECTIVE, reason).send();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ public:
|
|||
void onDirective(AlexaDirectiveHandler handler);
|
||||
|
||||
// The handlers of 1.x: "ReportState", "Event" for every other directive, and "DirectiveReceived", which is
|
||||
// called before either
|
||||
// called before either. A name that has a handler gets the new one. The name is not copied. A device keeps
|
||||
// MAX_EVENTS names; one more is refused with an error.
|
||||
void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&));
|
||||
|
||||
// Returns false when no handler has this name. warnIfMissing=false keeps that quiet.
|
||||
|
|
@ -142,7 +143,8 @@ public:
|
|||
// Hands a directive, {"header": ..., "endpoint": ..., "payload": ...}, to the handler of the device. The
|
||||
// device answers with the ErrorResponse INVALID_DIRECTIVE itself when it has no handler, and when the
|
||||
// directive is for a capability it does not have and the handler sent nothing. A handler that sends nothing
|
||||
// for a capability of the device leaves the directive unanswered; that is printed as an error.
|
||||
// for a capability of the device leaves the directive unanswered. For a handler of onDirective() that is
|
||||
// printed as an error; a handler of registerEvent() may answer from a later loop(), so it is a line at DEBUG.
|
||||
void handleDirective(const JsonDocument& message);
|
||||
|
||||
// A device without a transport gets none for its messages: send() says what is wrong
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ Print *AlexaLog::output = &Serial;
|
|||
void AlexaLog::setLevel(AlexaLogLevel newLevel)
|
||||
{
|
||||
level = newLevel;
|
||||
|
||||
// The lines above the ceiling are not in the program. Without this line a sketch that asks for them waits for
|
||||
// output that cannot come.
|
||||
if (static_cast<int>(newLevel) > ALEX2ESP_LOG_MAX)
|
||||
{
|
||||
ALEX2ESP_LOGE("log level %d asked for, the lines of this build end at level %d: build with -DALEX2ESP_LOG_MAX=%d",
|
||||
static_cast<int>(newLevel), ALEX2ESP_LOG_MAX, static_cast<int>(newLevel));
|
||||
}
|
||||
}
|
||||
|
||||
AlexaLogLevel AlexaLog::getLevel()
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ enum class AlexaLogLevel : uint8_t
|
|||
class AlexaLog
|
||||
{
|
||||
public:
|
||||
// A level above ALEX2ESP_LOG_MAX prints an error that names the build flag: its lines are not compiled in
|
||||
static void setLevel(AlexaLogLevel level);
|
||||
static AlexaLogLevel getLevel();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
// A 1.x name, kept for sketches that print the memory figures. The send and receive queues that lived here
|
||||
// belonged to the HTTP fallback and went with it in 1.2.0; the library's own diagnostics are in AlexaLog.h.
|
||||
// printMemoryInfo() is a utility of the sketch and not a line of the log: the library never calls it, and it
|
||||
// prints on Serial whatever the log level and ALEX2ESP_LOG_MAX are, because the sketch asked for the figures.
|
||||
class AlexaUtils
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue