Keep the root topic out of the log: print endpoint ids and topics without their root

The root topic identifies the account, and Serial logs get posted in issues and forums.
"repeated directive on <topic> ignored" and the lines for dropped directives printed the full
MQTT topic; they now start with the endpoint id. The lines for subscriptions and publishes print
the topic relative to the root (AlexaBridgeLogic::topicForLog), and begin() prints the length of
the root topic instead of its text. No line at any level prints the root topic, the username or
the password.
basicLight: RAM 34,476 -> 34,508 B, flash 338,281 -> 338,533 B; host tests 45 -> 47.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 18:45:18 +00:00
parent 2ed52c8721
commit d534f23f27
7 changed files with 79 additions and 25 deletions

View file

@ -137,12 +137,12 @@ An `ActionMapping` takes an optional third argument, the directive payload as JS
Everything goes over MQTT (port 1883 of `alex2mqtt.stormysdream.club`); the library makes no HTTP requests.
- **Session.** `begin()` starts SNTP (`pool.ntp.org`, `time.nist.gov`) and returns; `loop()` opens the MQTT session once Wi-Fi is up and the clock is set, or after 5 s of Wi-Fi without an answer, and subscribes to `<root>/discover` and `<root>/+/alexaDirective`. `getState()` is `CONNECTED` when the broker has acknowledged both subscriptions; `[Alex2ESP] error: the broker refused the subscription to <topic>` means that the root topic is not the one of the account. A session that ends or cannot be opened prints `[Alex2ESP] error: disconnected: <reason>; next attempt in N s` and is opened again after 1 s, then 2 s, 4 s and so on up to once a minute, for as long as Wi-Fi is up; the wait starts at 1 s again once a session is open. `the broker refused the username or the password` is the reason to look for when a board never shows up in Alexa. A sketch that sets the clock itself (its own `configTime()` with a time zone, an RTC) calls `alexClient.setTimeSource(false)` before `begin()`. The board has to reach an NTP server: DNS for the two names and outbound UDP port 123. Without the time of day the session still opens, the reports carry a `timeOfSample` in 1970, and the library prints `[Alex2ESP] error: the clock is not set, ...` when it connects and then at most once a minute while reports are sent.
- **Session.** `begin()` starts SNTP (`pool.ntp.org`, `time.nist.gov`) and returns; `loop()` opens the MQTT session once Wi-Fi is up and the clock is set, or after 5 s of Wi-Fi without an answer, and subscribes to `<root>/discover` and `<root>/+/alexaDirective`. `getState()` is `CONNECTED` when the broker has acknowledged both subscriptions; `[Alex2ESP] error: the broker refused the subscription to <root>/discover` means that the root topic is not the one of the account. A session that ends or cannot be opened prints `[Alex2ESP] error: disconnected: <reason>; next attempt in N s` and is opened again after 1 s, then 2 s, 4 s and so on up to once a minute, for as long as Wi-Fi is up; the wait starts at 1 s again once a session is open. `the broker refused the username or the password` is the reason to look for when a board never shows up in Alexa. A sketch that sets the clock itself (its own `configTime()` with a time zone, an RTC) calls `alexClient.setTimeSource(false)` before `begin()`. The board has to reach an NTP server: DNS for the two names and outbound UDP port 123. Without the time of day the session still opens, the reports carry a `timeOfSample` in 1970, and the library prints `[Alex2ESP] error: the clock is not set, ...` when it connects and then at most once a minute while reports are sent.
- **Discovery.** On `<root>/discover` the library answers with one discovery object per device on `<root>/discover_r`. The backend accepts one endpoint object per message and collects everything that arrives within 1 s for Alexa's discovery answer (up to 5 s for its proactive AddOrUpdate push), so all devices are published back to back from the next `loop()`. Each object sits on the heap (about 1 KB) until the MQTT client has sent it; when the client cannot take another one (free heap under 4 KB), the library prints `[Alex2ESP] discovery deferred at <endpointId>` and sends the rest from `loop()` as the queue drains, for up to 5 s after the request. `[Alex2ESP] error: discovery gave up: N device(s) not announced` means those devices missed this answer - on the backend's proactive discovery that can remove them from Alexa until the next one.
- **Directives.** The directive arrives as JSON on `<root>/<endpointId>/alexaDirective`. A directive larger than one TCP segment arrives in fragments, which are put together in one heap block that exists only until `loop()` has parsed it. `loop()` then fires `ReportState` or `Event` (and `DirectiveReceived`, if registered) with the directive: `directive["header"]`, `directive["endpoint"]`, `directive["payload"]`. Every call of `loop()` handles one directive, in the order of arrival. Up to eight directives wait for it: Alexa sends a group command ("turn off the kitchen") as one directive per endpoint, and they arrive faster than a busy sketch calls `loop()`. A directive that arrives twice (a broker that mirrors its topics delivers every message twice) is handled once; the repeat is recognised while it arrives and takes no place among the waiting ones.
- **Reports.** `send()` publishes the report on `<root>/<endpointId>/alexaResponce` at once. The backend waits 7 s for it, so answer from the event handler. Every property carries the board's UTC time as `timeOfSample`.
- **Limits.** A directive may be 2047 bytes (`ALEX2ESP_MAX_DIRECTIVE`), a report or the discovery object of one device 3071 (`ALEX2ESP_MAX_MESSAGE`). The second limit is the first plus 1024 unless it is set: an answer repeats the `correlationToken` of its directive, which is most of a large directive, and adds 140 to 170 bytes per property, so the largest directive can be answered with six properties. Eight directives (`ALEX2ESP_MAX_QUEUED_DIRECTIVES`) of 8188 bytes together (`ALEX2ESP_MAX_QUEUED_BYTES`, four times the largest directive) may wait for `loop()`; one that finds no place is dropped with `[Alex2ESP] error: directive of N bytes on <topic> dropped: ...`. `-D<name>=<value>` in `build_flags` changes a limit. `send()` returns `false` when the report was not sent: no session with the broker, the MQTT client or the heap cannot take it, or it is too large. Nothing is ever sent truncated, and nothing is dropped without a line on Serial.
- **Serial output.** Every line of the library starts with `[Alex2ESP]`, a problem with `[Alex2ESP] error:`. `alexClient.setLogLevel(AlexaLogLevel::ERROR)` leaves only the problems, `AlexaLogLevel::NONE` nothing; the default, `AlexaLogLevel::INFO`, adds the session, discovery and one line per directive (`[Alex2ESP] ESP-01 <- Alexa.PowerController.TurnOn`). `AlexaLogLevel::DEBUG` (sizes and free heap per message) has to be compiled in with `-DALEX2ESP_LOG_MAX=3`; `-DALEX2ESP_LOG_MAX=0` compiles every line out. Credentials and correlation tokens are never printed. A sketch that defines a macro named `DEBUG`, `ERROR` or `INFO` cannot write the level of that name; it passes the number instead, for example `alexClient.setLogLevel(static_cast<AlexaLogLevel>(3))` for `DEBUG`.
- **Serial output.** Every line of the library starts with `[Alex2ESP]`, a problem with `[Alex2ESP] error:`. `alexClient.setLogLevel(AlexaLogLevel::ERROR)` leaves only the problems, `AlexaLogLevel::NONE` nothing; the default, `AlexaLogLevel::INFO`, adds the session, discovery and one line per directive (`[Alex2ESP] ESP-01 <- Alexa.PowerController.TurnOn`). `AlexaLogLevel::DEBUG` (sizes and free heap per message) has to be compiled in with `-DALEX2ESP_LOG_MAX=3`; `-DALEX2ESP_LOG_MAX=0` compiles every line out. The username, the password, the root topic and correlation tokens are never printed, at any level, so a log can be posted as it is: a topic appears without its root (`ESP-01/alexaResponce`), a directive under its endpoint id. A sketch that defines a macro named `DEBUG`, `ERROR` or `INFO` cannot write the level of that name; it passes the number instead, for example `alexClient.setLogLevel(static_cast<AlexaLogLevel>(3))` for `DEBUG`.
Boards that run 1.1.0 or older keep working: the backend still publishes the token on `<root>/<endpointId>/alexaDirective_e` and serves the HTTP routes they use.
@ -246,10 +246,10 @@ Behaviour changes:
- Reports leave over MQTT. `send()` publishes on `<root>/<endpointId>/alexaResponce` at once, where 1.1.0 queued the report for an HTTP POST from a later `loop()`. It returns `false` when there is no session with the broker, when the MQTT client or the heap cannot take the report, or when the report is over 3071 bytes (`ALEX2ESP_MAX_MESSAGE`, by default 1024 more than the largest directive, so that every directive that is accepted can be answered); each case prints its reason. The 5-slot send queue is gone.
- `timeOfSample` is the board's own time in UTC, for example `2026-09-28T13:05:09Z`. 1.1.0 sent the placeholder `{REPLACE_WITH_DATETIME}`, which only the backend's HTTP route replaced. `begin()` starts SNTP (`pool.ntp.org`, `time.nist.gov`) and no longer connects itself: `loop()` opens the MQTT session once the clock is set, or after 5 s without an answer, so the session comes up a few seconds later than before. `alexClient.setTimeSource(false)` before `begin()` leaves the clock to the sketch. `AddContextProp()` fills `timeOfSample` in when the property has none or carries the old placeholder. The board needs an answer from an NTP server (DNS, outbound UDP port 123), which 1.1.0 did not: without one the reports carry a time in 1970, and the library says so when it connects and then at most once a minute while reports are sent.
- A directive is parsed and handed to the sketch from `loop()`, one per call, in the order of arrival. Up to eight directives of 8188 bytes together wait for it on the heap (`ALEX2ESP_MAX_QUEUED_DIRECTIVES`, `ALEX2ESP_MAX_QUEUED_BYTES`; 1.1.0 queued five tokens), because Alexa sends a group command as one directive per endpoint. A directive that finds no place is dropped, as is one over 2047 bytes (`ALEX2ESP_MAX_DIRECTIVE`); both print an error.
- A directive that arrives twice is handled once: the Alex2MQTT broker currently delivers every message twice through a mirror. The repeat is recognised while it arrives, by a hash of its bytes, and takes no place among the waiting directives; a directive that comes again with other bytes is recognised by its `messageId`. The last 16 directives are remembered; the repeat prints `repeated directive ... ignored`.
- A directive that arrives twice is handled once: the Alex2MQTT broker currently delivers every message twice through a mirror. The repeat is recognised while it arrives, by a hash of its bytes, and takes no place among the waiting directives; a directive that comes again with other bytes is recognised by its `messageId`. The last 16 directives are remembered; the repeat prints `<endpointId>: repeated directive ignored`.
- The subscription delivers the directives of every endpoint of the account. Those for endpoints of another board are recognised by their topic and neither buffered nor parsed.
- Discovery is answered from `loop()`, not inside the MQTT callback. A discovery object over 3071 bytes (`ALEX2ESP_MAX_MESSAGE`) is refused with an error; the other devices are still announced.
- `getState()` stays `INITIALIZED` until the first connect, and becomes `CONNECTED` when the broker has acknowledged both subscriptions (1.1.0: the first of them). A subscription that the broker refuses prints an error that names the topic and what to check, the root topic passed to `begin()`; the state stays `SUBSCRIBING`. When the MQTT client does not take a subscription, the session is closed and the next one subscribes again.
- `getState()` stays `INITIALIZED` until the first connect, and becomes `CONNECTED` when the broker has acknowledged both subscriptions (1.1.0: the first of them). A subscription that the broker refuses prints an error that names the topic (without its root) and what to check, the root topic passed to `begin()`; the state stays `SUBSCRIBING`. When the MQTT client does not take a subscription, the session is closed and the next one subscribes again.
- Serial output goes through one log with levels. `alexClient.setLogLevel()` takes `AlexaLogLevel::NONE`, `ERROR`, `INFO` (the default) or `DEBUG`; `-DALEX2ESP_LOG_MAX=<0..3>` in `build_flags` sets the highest level that is compiled in (default 2, `INFO`). This replaces the `Alex2ESP_DEBUG` define inside `AlexaUtils.cpp`. 1.1.0 printed the memory figures for every MQTT message and the whole directive, correlation token included, for every directive; both are gone. A sketch or a build flag that defines `DEBUG`, `ERROR` or `INFO` as a macro (`#define DEBUG 1`, `-DDEBUG`) still compiles: the library sets those macros aside where it declares its levels and passes a level by its number everywhere else.
- The session is opened again whatever ended it, and the reason is printed in words: `disconnected: the broker refused the username or the password, check the two passed to begin(); next attempt in 1 s`. 1.1.0 reconnected every 5 s after a lost TCP connection only and printed nothing; a board with a wrong password, or one that met the broker while it was restarting, stayed offline until it was reset. The wait is 1 s after a session ended and doubles with every attempt that fails, up to 60 s. Nothing is tried while Wi-Fi is down, and the first connect waits for Wi-Fi as well (`waiting for Wi-Fi: ...` is printed once). A connect that has no answer after 30 s counts as failed. The MQTT keep-alive is 30 s (1.1.0: the 15 s of the MQTT client).
- `getDevice()` before `begin()` prints an error: the device would have no root topic. A second `begin()` is ignored with an error.
@ -257,9 +257,9 @@ Behaviour changes:
- New: `Alex2ESP::setLogLevel()`, `Alex2ESP::setTimeSource()`, `AlexaDevice::hasEndpointId()`, `AlexaLog`, `AlexaSendResult`, and `AlexaTransport`, the interface a device sends and stamps its reports through. `Alex2ESP` implements it with `publish(topic, document)`, which sends a `JsonDocument` on the session of the library under the same checks as a report and returns an `AlexaSendResult`, and `timestamp(buffer, size)`, which writes the current time as `timeOfSample` has it.
- Removed: the queues and buffers of `AlexaUtils` (`enqueue`, `dequeue`, `dequeueVals`, `enqueueReceive`, `dequeueReceive`, `isQueueEmpty`, `isQueueFull`, `isReceiveQueueEmpty`, `isReceiveQueueFull`, `receivePayload`, `nextMessageId`) and its `log`/`logln`, which printed nothing unless the library was edited; `AlexaUtils::printMemoryInfo()` stays. `MAX_STATUS_REPORT_SIZE` (the limit is `ALEX2ESP_MAX_MESSAGE`). The library no longer includes `ESP8266HTTPClient`.
Memory: `examples/basicLight.cpp` for a D1 mini takes 34,476 bytes of static RAM (1.1.0: 52,768) and 338,281 bytes of flash (1.1.0: 350,885), as PlatformIO reports them (espressif8266 4.2.1, Arduino core 3.1.2). The static RAM was the five 2 KB queue slots, three more 2 KB buffers and the two HTTP clients. SNTP and the time stamp are 1.8 KB of the flash figure.
Memory: `examples/basicLight.cpp` for a D1 mini takes 34,508 bytes of static RAM (1.1.0: 52,768) and 338,533 bytes of flash (1.1.0: 350,885), as PlatformIO reports them (espressif8266 4.2.1, Arduino core 3.1.2). The static RAM was the five 2 KB queue slots, three more 2 KB buffers and the two HTTP clients. SNTP and the time stamp are 1.8 KB of the flash figure.
Tests: `pio test -e native` in the repository runs 45 host tests of the receive and publish logic (reassembly of fragments, the directives that wait for `loop()`, repeated directives, the size limits, a heap without room, the wait between reconnects, topics, time stamps). No board is needed.
Tests: `pio test -e native` in the repository runs 47 host tests of the receive and publish logic (reassembly of fragments, the directives that wait for `loop()`, repeated directives, the size limits, a heap without room, the wait between reconnects, topics, time stamps). No board is needed.
Packaging: `library.json`, `library.properties` and the `softwareVersion` and `firmwareVersion` that a device reports in discovery say 1.2.0; the library has the number in one place, `ALEX2ESP_VERSION` in `src/AlexaVersion.h`. The `platformio.ini` of the repository is for the host tests; a sketch does not need it.

View file

@ -100,8 +100,9 @@ void Alex2ESP::begin(const char *username, const char *password, const char *roo
discoverTopicSend = this->rootTopic + "/discover_r";
directiveFilter = this->rootTopic + "/+/alexaDirective";
// Log the root topic (never the credentials)
ALEX2ESP_LOGI("root topic %s", this->rootTopic.c_str());
// The root topic identifies the account, like the credentials: the log has its length, which is enough to see
// that one was passed
ALEX2ESP_LOGI("root topic of %u characters", (unsigned)this->rootTopic.length());
// Reports carry the time of their samples, so the board needs the time of day: UTC from SNTP
if (useSntp)
@ -278,11 +279,11 @@ void Alex2ESP::onSubscribe(uint16_t packetId, uint8_t qos)
if (qos == SUBSCRIPTION_REFUSED)
{
// The session stays open and the state SUBSCRIBING: the broker would refuse the same topic again
ALEX2ESP_LOGE("the broker refused the subscription to %s: check the root topic passed to begin()", topic.c_str());
ALEX2ESP_LOGE("the broker refused the subscription to <root>/%s: check the root topic passed to begin()", logName(topic.c_str()));
return;
}
ALEX2ESP_LOGD("subscribed to %s", topic.c_str());
ALEX2ESP_LOGD("subscribed to <root>/%s", logName(topic.c_str()));
if (subscriptionsPending > 0 && --subscriptionsPending == 0)
{
state = Alex2ESPState::CONNECTED;
@ -337,7 +338,7 @@ void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessagePrope
// boards: not an error, and not worth a buffer
if (index == 0)
{
ALEX2ESP_LOGD("directive on %s is for another board", topic);
ALEX2ESP_LOGD("%.*s: directive for another board", (int)endpointLength, endpointId);
}
return;
}
@ -345,22 +346,22 @@ void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessagePrope
switch (directives.append(payload, length, index, total))
{
case AlexaDirectiveBuffer::Result::TOO_LARGE:
ALEX2ESP_LOGE("directive of %u bytes on %s dropped: the limit is %u (ALEX2ESP_MAX_DIRECTIVE)", (unsigned)total, topic, (unsigned)ALEX2ESP_MAX_DIRECTIVE);
ALEX2ESP_LOGE("%.*s: directive of %u bytes dropped: the limit is %u (ALEX2ESP_MAX_DIRECTIVE)", (int)endpointLength, endpointId, (unsigned)total, (unsigned)ALEX2ESP_MAX_DIRECTIVE);
break;
case AlexaDirectiveBuffer::Result::QUEUE_FULL:
ALEX2ESP_LOGE("directive of %u bytes on %s dropped: %u directives, %u bytes, already wait for loop()", (unsigned)total, topic, (unsigned)directives.waitingDirectives(), (unsigned)directives.waitingBytes());
ALEX2ESP_LOGE("%.*s: directive of %u bytes dropped: %u directives, %u bytes, already wait for loop()", (int)endpointLength, endpointId, (unsigned)total, (unsigned)directives.waitingDirectives(), (unsigned)directives.waitingBytes());
break;
case AlexaDirectiveBuffer::Result::NO_MEMORY:
ALEX2ESP_LOGE("directive of %u bytes on %s dropped: no memory (%u bytes of heap free)", (unsigned)total, topic, (unsigned)ESP.getFreeHeap());
ALEX2ESP_LOGE("%.*s: directive of %u bytes dropped: no memory (%u bytes of heap free)", (int)endpointLength, endpointId, (unsigned)total, (unsigned)ESP.getFreeHeap());
break;
case AlexaDirectiveBuffer::Result::OUT_OF_ORDER:
ALEX2ESP_LOGE("directive on %s dropped: the fragment at byte %u of %u does not continue it", topic, (unsigned)index, (unsigned)total);
ALEX2ESP_LOGE("%.*s: directive dropped: the fragment at byte %u of %u does not continue it", (int)endpointLength, endpointId, (unsigned)index, (unsigned)total);
break;
case AlexaDirectiveBuffer::Result::EMPTY:
ALEX2ESP_LOGE("directive on %s dropped: it is empty", topic);
ALEX2ESP_LOGE("%.*s: directive dropped: it is empty", (int)endpointLength, endpointId);
break;
case AlexaDirectiveBuffer::Result::DUPLICATE:
ALEX2ESP_LOGI("repeated directive on %s ignored", topic);
ALEX2ESP_LOGI("%.*s: repeated directive ignored", (int)endpointLength, endpointId);
break;
case AlexaDirectiveBuffer::Result::INCOMPLETE:
case AlexaDirectiveBuffer::Result::COMPLETE:
@ -509,7 +510,7 @@ AlexaSendResult Alex2ESP::publish(const char *topic, JsonDocument &doc)
AlexaSendResult result = trySend(topic, doc, &length);
if (result == AlexaSendResult::OK)
{
ALEX2ESP_LOGD("%u bytes -> %s, %u bytes of heap free", (unsigned)length, topic, (unsigned)ESP.getFreeHeap());
ALEX2ESP_LOGD("%u bytes -> %s, %u bytes of heap free", (unsigned)length, logName(topic), (unsigned)ESP.getFreeHeap());
}
else
{
@ -582,28 +583,33 @@ AlexaSendResult Alex2ESP::trySend(const char *topic, JsonDocument &doc, size_t *
return result;
}
const char *Alex2ESP::logName(const char *topic) const
{
return AlexaBridgeLogic::topicForLog(topic, rootTopic.c_str());
}
void Alex2ESP::logRefusal(const char *topic, AlexaSendResult result, size_t length)
{
switch (result)
{
case AlexaSendResult::NOT_CONNECTED:
ALEX2ESP_LOGE("%s: %u bytes not sent, no session with the broker", topic, (unsigned)length);
ALEX2ESP_LOGE("%s: %u bytes not sent, no session with the broker", logName(topic), (unsigned)length);
break;
case AlexaSendResult::REFUSED:
ALEX2ESP_LOGE("%s: %u bytes not sent, the MQTT client or the heap is full (%u bytes free)", topic, (unsigned)length, (unsigned)ESP.getFreeHeap());
ALEX2ESP_LOGE("%s: %u bytes not sent, the MQTT client or the heap is full (%u bytes free)", logName(topic), (unsigned)length, (unsigned)ESP.getFreeHeap());
break;
case AlexaSendResult::TOO_LARGE:
if (length == 0)
{
ALEX2ESP_LOGE("%s: not sent, the message ran out of memory while it was built", topic);
ALEX2ESP_LOGE("%s: not sent, the message ran out of memory while it was built", logName(topic));
}
else
{
ALEX2ESP_LOGE("%s: %u bytes not sent, the limit is %u (ALEX2ESP_MAX_MESSAGE)", topic, (unsigned)length, (unsigned)ALEX2ESP_MAX_MESSAGE);
ALEX2ESP_LOGE("%s: %u bytes not sent, the limit is %u (ALEX2ESP_MAX_MESSAGE)", logName(topic), (unsigned)length, (unsigned)ALEX2ESP_MAX_MESSAGE);
}
break;
case AlexaSendResult::EMPTY:
ALEX2ESP_LOGE("%s: not sent, the message is empty", topic);
ALEX2ESP_LOGE("%s: not sent, the message is empty", logName(topic));
break;
case AlexaSendResult::OK:
break;

View file

@ -130,6 +130,7 @@ private:
AlexaDevice *findDevice(const char *endpointId, size_t length);
AlexaSendResult trySend(const char *topic, JsonDocument &doc, size_t *length);
void logRefusal(const char *topic, AlexaSendResult result, size_t length);
const char *logName(const char *topic) const; // The topic as the log may print it: without the root topic
};
#endif // ALEX2ESP_H

View file

@ -280,6 +280,22 @@ const char *AlexaBridgeLogic::directiveEndpoint(const char *topic, const char *r
return endpointId;
}
const char *AlexaBridgeLogic::topicForLog(const char *topic, const char *rootTopic)
{
static const char outside[] = "(topic outside the root)";
if (topic == nullptr || rootTopic == nullptr)
{
return outside;
}
size_t rootLength = strlen(rootTopic);
if (strncmp(topic, rootTopic, rootLength) != 0 || topic[rootLength] != '/')
{
return outside;
}
return topic + rootLength + 1;
}
AlexaSendResult AlexaBridgeLogic::checkMessage(const JsonDocument &doc, size_t maxLength, size_t *length)
{
if (length != nullptr)

View file

@ -206,6 +206,11 @@ namespace AlexaBridgeLogic
// topic that 1.x boards use.
const char *directiveEndpoint(const char *topic, const char *rootTopic, size_t *length);
// The topic as a log line may print it. The root topic identifies the account and a log gets posted in forums
// and issues, so "<root>/ESP-01/alexaResponce" is printed as "ESP-01/alexaResponce". A topic that is not under
// the root is not printed at all, it may still begin with it: the result is a fixed text then.
const char *topicForLog(const char *topic, const char *rootTopic);
// Decides whether a document may be published and measures it: TOO_LARGE when it is over maxLength bytes or
// ran out of memory while it was built (it would be sent truncated), EMPTY when it holds nothing, otherwise OK
// with the serialised size in length.

View file

@ -1,7 +1,7 @@
// Serial diagnostics of the library. A line is printed when its level is within the level chosen at run time
// (Alex2ESP::setLogLevel, INFO unless changed) and within the ceiling compiled in (ALEX2ESP_LOG_MAX); a line above
// the ceiling costs no flash. Whatever the library refuses or drops is reported at ERROR. Credentials and
// correlation tokens are never printed.
// the ceiling costs no flash. Whatever the library refuses or drops is reported at ERROR. Credentials, the root
// topic (it identifies the account) and correlation tokens are never printed.
#ifndef ALEXA_LOG_H
#define ALEXA_LOG_H

View file

@ -753,6 +753,30 @@ void test_other_topics_are_not_directive_topics()
TEST_ASSERT_NULL(AlexaBridgeLogic::directiveEndpoint("root/ESP-01/alexaDirective", "root", nullptr));
}
void test_log_prints_a_topic_without_its_root()
{
TEST_ASSERT_EQUAL_STRING("ESP-01/alexaResponce", AlexaBridgeLogic::topicForLog("root/ESP-01/alexaResponce", "root"));
TEST_ASSERT_EQUAL_STRING("discover", AlexaBridgeLogic::topicForLog("root/discover", "root"));
TEST_ASSERT_EQUAL_STRING("+/alexaDirective", AlexaBridgeLogic::topicForLog("a/b/+/alexaDirective", "a/b"));
// An empty root topic (begin() with "") still has the separator
TEST_ASSERT_EQUAL_STRING("ESP-01/alexaResponce", AlexaBridgeLogic::topicForLog("/ESP-01/alexaResponce", ""));
}
void test_log_never_prints_the_root_topic()
{
const char *topics[] = {"root", "root/", "rootless/ESP-01/alexaResponce", "other/root/discover", "", nullptr};
for (const char *topic : topics)
{
const char *printed = AlexaBridgeLogic::topicForLog(topic, "root");
TEST_ASSERT_NOT_NULL(printed);
TEST_ASSERT_NULL(strstr(printed, "root/"));
TEST_ASSERT_NULL(strstr(printed, "rootless"));
}
TEST_ASSERT_EQUAL_STRING("", AlexaBridgeLogic::topicForLog("root/", "root"));
TEST_ASSERT_NOT_NULL(AlexaBridgeLogic::topicForLog("root/discover", nullptr));
}
// --- reconnect ---
void test_wait_doubles_from_a_second_to_a_minute()
@ -900,6 +924,8 @@ int main(int, char **)
RUN_TEST(test_endpoint_is_taken_from_the_directive_topic);
RUN_TEST(test_other_topics_are_not_directive_topics);
RUN_TEST(test_log_prints_a_topic_without_its_root);
RUN_TEST(test_log_never_prints_the_root_topic);
RUN_TEST(test_wait_doubles_from_a_second_to_a_minute);
RUN_TEST(test_open_session_starts_the_wait_over);