Close the session when the MQTT client does not take a subscription; name the root topic when the broker refuses one
Two error lines of 8ea7778 did not help the reader act.
"the MQTT client refused to subscribe (N bytes of heap free): no directives until it reconnects": nothing reconnected. The session stayed open without subscriptions and the state SUBSCRIBING for good, because the reconnect only acts on a session that has ended. The free heap was beside the point: AsyncMqttClient 0.9.0 returns 0 from subscribe() only when its session is not open. The bridge now prints "the MQTT client did not take the subscriptions: closing the session, the next one subscribes again" and calls disconnect(), which queues the DISCONNECT packet; the end of the session then starts the reconnect.
"the broker refused the subscription to <topic>": it did not say what to check. The usual cause is a root topic that does not belong to the account, so the line ends with ": check the root topic passed to begin()". The session stays open and the state SUBSCRIBING, since the broker would refuse the same topic again; the comment says so.
Measured with the bridge built for the host against a fake MQTT client (not in the repository): subscribe() returning 0 leads to one disconnect() and, after the session has ended, to one connect() within 7 s and a session that reaches CONNECTED; a SUBACK with 0x80 prints the new line and leaves the session open.
examples/basicLight.cpp for d1_mini, static RAM / flash in bytes: 34,452 / 337,401 -> 34,452 / 337,461, no warnings. 41 host tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c5b4802d65
commit
5b090a43dd
2 changed files with 9 additions and 5 deletions
|
|
@ -137,7 +137,7 @@ 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 the clock is set, or after 5 s without an answer, and subscribes to `<root>/discover` and `<root>/+/alexaDirective`. `getState()` is `CONNECTED` when the broker has acknowledged both subscriptions. 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 the clock is set, or after 5 s 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 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`.
|
||||
|
|
@ -249,13 +249,13 @@ Behaviour changes:
|
|||
- 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`.
|
||||
- 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 refused subscription prints an error.
|
||||
- `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.
|
||||
- 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.
|
||||
- `getDevice()` before `begin()` prints an error: the device would have no root topic. A second `begin()` is ignored with an error.
|
||||
- New: `Alex2ESP::setLogLevel()`, `Alex2ESP::setTimeSource()`, `AlexaDevice::hasEndpointId()`, `AlexaLog`, `AlexaSendResult`.
|
||||
- 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,452 bytes of static RAM (1.1.0: 52,768) and 337,401 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,452 bytes of static RAM (1.1.0: 52,768) and 337,461 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 41 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, topics, time stamps). No board is needed.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue