The changelog of 8ea7778 left out a change a sketch can observe. AlexaDevice and AlexaStatusMessage gained a trailing AlexaTransport* argument (default nullptr). A sketch that constructs either one itself still compiles, but send() returns false with "report for <id> not sent: its device was not created by getDevice()", where 1.1.0 queued and delivered the report. The changelog now has that as a behaviour change, with what to use instead.
The "New:" line also lacked AlexaTransport and the two public methods it gives Alex2ESP, publish() and timestamp(), although keywords.txt lists all three. They are usable from a sketch, so they are described and stay in keywords.txt.
Text only: no source file changed, examples/basicLight.cpp builds at 34,452 / 337,461 bytes (static RAM / flash) as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
292 lines
25 KiB
Markdown
292 lines
25 KiB
Markdown
# Alex2ESP
|
||
|
||
Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 microcontrollers with Amazon Alexa smart home APIs through the [Alex2MQTT](https://alex2mqtt.stormysdream.club/) skill. The library simplifies the process of creating Alexa-compatible devices using MQTT.
|
||
|
||
**Supported target: ESP8266** (developed and tested on a Wemos D1 mini). ESP32: untested - the code carries include guards for it, but it has never been compiled or run there.
|
||
|
||
For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/).
|
||
|
||
---
|
||
|
||
|
||
|
||
## Features
|
||
- Supports "All" Alexa smart home capabilities.
|
||
- Provides action mapping for advanced directive customization including Open,Close,Raise,and Lower commands.
|
||
- Allows custom JSON injection in both discovery and state reporting to allow for unsupported devices.
|
||
- Event-driven architecture for handling Alexa directives.
|
||
---
|
||
|
||
## Alternatives
|
||
|
||
one of the most popular library for controlling esp like devices using alexa is [FauxmoESP](https://github.com/vintlabs/fauxmoESP)
|
||
however FauxmoESP works by emulating a light bulb so it is limited in what commands it accepts.
|
||
|
||
---
|
||
|
||
## Quick Start
|
||
### Installation
|
||
|
||
The library lives on Forgejo at https://git.stormysdream.club/platformio/Alex2ESP (public). It is not in the PlatformIO registry or the Arduino Library Manager: install it from git.
|
||
|
||
#### PlatformIO
|
||
|
||
```ini
|
||
[env:d1_mini]
|
||
platform = espressif8266
|
||
board = d1_mini
|
||
framework = arduino
|
||
monitor_speed = 74880
|
||
lib_deps =
|
||
https://git.stormysdream.club/platformio/Alex2ESP.git
|
||
marvinroger/AsyncMqttClient@^0.9.0
|
||
bblanchon/ArduinoJson@^7
|
||
```
|
||
|
||
To pin a release instead of `main`, append the tag: `https://git.stormysdream.club/platformio/Alex2ESP.git#v1.1.0`. AsyncMqttClient pulls in ESPAsyncTCP by itself; if PlatformIO notes that more than one `ESPAsyncTCP` package matches, add `esp32async/ESPAsyncTCP@^2.0.0` to `lib_deps` to pick one explicitly. The examples print at 74880 baud, the ESP8266 boot-ROM rate, so the boot messages stay readable in the same monitor.
|
||
|
||
#### Arduino IDE
|
||
|
||
Download the repository as a ZIP (https://git.stormysdream.club/platformio/Alex2ESP/archive/main.zip) and add it with *Sketch -> Include Library -> Add .ZIP Library*. Install **AsyncMqttClient**, **ArduinoJson** (7.x) and **ESP Async TCP** (by ESP32Async) from the Library Manager. Install the ESP8266 board package and select your board (for example *LOLIN(WEMOS) D1 R2 & mini*).
|
||
|
||
Then include the library in your project:
|
||
```cpp
|
||
#include <Alex2ESP.h>
|
||
```
|
||
|
||
Complete sketches are in [`examples/`](examples/): `basicLight.cpp`, `lightWithBrightness.cpp`, `lightWithColorTemp.cpp`, `tempSensor.cpp` and `blindControl.cpp`. Every example joins Wi-Fi with `WiFi.begin(WIFI_SSID, WIFI_PASSWORD)`; fill in the SSID, the password and your Alex2MQTT credentials before flashing.
|
||
|
||
---
|
||
|
||
## Creating a Basic Device
|
||
### Example: Light Control
|
||
|
||
Here’s how to create a simple device that controls a light:
|
||
|
||
First create an instance of the alexa client and initilize it with your MQTT Credentials
|
||
```cpp
|
||
Alex2ESP alexClient;
|
||
```
|
||
|
||
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon. `begin()` takes the MQTT username, the MQTT password and the root topic, in that order (the same order as alex2node's `new Alex2MQTT(username, password, rootTopic)`).
|
||
```cpp
|
||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||
```
|
||
|
||
Once initilized you can begin to add virtual devices, in this example we add a power controller to toggle a light
|
||
```cpp
|
||
|
||
AlexaDevice* device1 = alexClient.getDevice("Test Lamp", "ESP-01");
|
||
device1->setDisplayCategory(DisplayCategory::LIGHT);
|
||
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
|
||
|
||
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
|
||
// Build and send status report
|
||
device1->buildStatusMessage(directive["header"]["correlationToken"])
|
||
.AddHealthProp(EndpointHealth::OK)
|
||
.AddPowerControllerProp(outputState)
|
||
.send();
|
||
});
|
||
|
||
// Register event listener for "Event" directive to handle state changes
|
||
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
|
||
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
|
||
if (directive["header"]["name"] == "TurnOn") {
|
||
outputState = PowerController::ON;
|
||
Serial.println("Turning ON");
|
||
} else if (directive["header"]["name"] == "TurnOff") {
|
||
outputState = PowerController::OFF;
|
||
Serial.println("Turning OFF");
|
||
}
|
||
|
||
// Send the updated state after change
|
||
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
|
||
.AddHealthProp(EndpointHealth::OK)
|
||
.AddPowerControllerProp(outputState)
|
||
.send();
|
||
}
|
||
});
|
||
```
|
||
|
||
The pointers returned by `getDevice()` and `addCapability()` stay valid for the lifetime of the client, so keeping them in globals and adding more devices or capabilities later is fine.
|
||
|
||
---
|
||
### Example: Toggle Controller for Blinds
|
||
Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller
|
||
For a comprehensive list of Alexa actions and mappings, visit the [Alexa Developer Documentation](https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping)
|
||
|
||
```cpp
|
||
AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01");
|
||
device->setDisplayCategory(DisplayCategory::INTERIOR_BLIND);
|
||
|
||
AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
|
||
toggleController->setInstance("ESP-01.Toggle");
|
||
toggleController->addFriendlyName("Bedroom Blinds", "en-US");
|
||
|
||
ActionMapping closeMapping({AlexaActions::Close}, "TurnOn");
|
||
ActionMapping openMapping({AlexaActions::Open}, "TurnOff");
|
||
toggleController->addActionMapping(closeMapping);
|
||
toggleController->addActionMapping(openMapping);
|
||
```
|
||
|
||
An `ActionMapping` takes an optional third argument, the directive payload as JSON text (for example `"{\"rangeValue\": 0}"` for a RangeController). It is emitted as an object in the discovery response and omitted when empty.
|
||
|
||
---
|
||
|
||
## How it talks to Alex2MQTT
|
||
|
||
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; `[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`.
|
||
- **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`.
|
||
|
||
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.
|
||
|
||
---
|
||
|
||
## Interface Types
|
||
| Alexa Interface Type | Status |
|
||
|-----------------------------------------------------|-------------|
|
||
| AlexaInterfaceType::ENDPOINT_HEALTH | Fully Supported |
|
||
| AlexaInterfaceType::POWER_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType::BRIGHTNESS_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType::TOGGLE_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType::TEMPERATURE_SENSOR | Fully Supported |
|
||
| AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType::AUTOMATION_MANAGEMENT | Supported* |
|
||
| AlexaInterfaceType::CHANNEL_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::COLOR_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::CONTACT_SENSOR | Supported* |
|
||
| AlexaInterfaceType::APPLICATION_STATE_REPORTER | Supported* |
|
||
| AlexaInterfaceType::AUDIO_PLAY_QUEUE | Supported* |
|
||
| AlexaInterfaceType::AUTHORIZATION_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA | Supported* |
|
||
| AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::CAMERA_STREAM_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::COMMISSIONABLE | Supported* |
|
||
| AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER | Supported* |
|
||
| AlexaInterfaceType::COOKING | Supported* |
|
||
| AlexaInterfaceType::DATA_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::DEVICE_USAGE_ESTIMATION | Supported* |
|
||
| AlexaInterfaceType::DEVICE_USAGE_METER | Supported* |
|
||
| AlexaInterfaceType::DOORBELL_EVENT_SOURCE | Supported* |
|
||
| AlexaInterfaceType::EQUALIZER_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::INPUT_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::INVENTORY_LEVEL_SENSOR | Supported* |
|
||
| AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR | Supported* |
|
||
| AlexaInterfaceType::INVENTORY_USAGE_SENSOR | Supported* |
|
||
| AlexaInterfaceType::KEYPAD_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::LAUNCHER | Supported* |
|
||
| AlexaInterfaceType::LOCK_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::MEDIA_PLAYBACK | Supported* |
|
||
| AlexaInterfaceType::MEDIA_SEARCH | Supported* |
|
||
| AlexaInterfaceType::MODE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::MOTION_SENSOR | Supported* |
|
||
| AlexaInterfaceType::PERCENTAGE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::PLAYBACK_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::PLAYBACK_STATE_REPORTER | Supported* |
|
||
| AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE | Supported* |
|
||
| AlexaInterfaceType::RANGE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::RECORD_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::REMOTE_VIDEO_PLAYER | Supported* |
|
||
| AlexaInterfaceType::RTC_SESSION_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::SCENE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::SECURITY_PANEL_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::SEEK_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::SIMPLE_EVENT_SOURCE | Supported* |
|
||
| AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR | Supported* |
|
||
| AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER | Supported* |
|
||
| AlexaInterfaceType::SPEAKER | Supported* |
|
||
| AlexaInterfaceType::STEP_SPEAKER | Supported* |
|
||
| AlexaInterfaceType::THERMOSTAT_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* |
|
||
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* |
|
||
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE | Supported* |
|
||
| AlexaInterfaceType::TIME_HOLD_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::UI_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType::USER_PREFERENCE | Supported* |
|
||
| AlexaInterfaceType::VIDEO_RECORDER | Supported* |
|
||
| AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER | Supported* |
|
||
|
||
|
||
(*Partial support or limited implementation advanced configuration is required)
|
||
|
||
---
|
||
|
||
|
||
## Advanced usage
|
||
For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object.
|
||
|
||
For instance, to manually report the state of a PowerController, you can use the following code:
|
||
|
||
```cpp
|
||
JsonDocument doc;
|
||
|
||
doc["namespace"] = "Alexa.PowerController";
|
||
doc["name"] = "powerState";
|
||
doc["value"] = "ON";
|
||
doc["uncertaintyInMilliseconds"] = 0;
|
||
AddContextProp(doc.as<JsonObject>())
|
||
```
|
||
|
||
`AddContextProp` sets `timeOfSample` to the current time when the object has none. (Until 1.1.0 a sketch wrote `"{REPLACE_WITH_DATETIME}"` there for the server to replace; the library now replaces that itself.)
|
||
|
||
---
|
||
|
||
## Changelog
|
||
|
||
### 1.2.0
|
||
|
||
Behaviour changes:
|
||
- Directives arrive over MQTT. The library subscribes to `<root>/+/alexaDirective`, where Alex2MQTT has always published the whole directive, instead of fetching it over HTTP with the token from `<root>/<endpointId>/alexaDirective_e`. The HTTP detour dates from 2024, when a directive larger than one TCP segment reached the MQTT callback in pieces; the pieces are now put together by their offset and the total length, in one heap block that lives until `loop()` has parsed the directive. `loop()` no longer stalls for two HTTP round trips per directive.
|
||
- 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`.
|
||
- 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.
|
||
- 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.
|
||
- A report is sent through the client that created its device. An `AlexaDevice` or an `AlexaStatusMessage` that a sketch constructs itself has no client: `send()` returns `false` and prints `report for <endpointId> not sent: its device was not created by getDevice()`, where 1.1.0 queued the report. Use `getDevice()` and `buildStatusMessage()`. Both constructors take the client as an optional last argument, an `AlexaTransport*`.
|
||
- 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,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.
|
||
|
||
Boards that run 1.1.0 are not affected: the backend keeps the token topic and the HTTP routes.
|
||
|
||
### 1.1.0
|
||
|
||
Behaviour changes:
|
||
- `begin()` now takes `(username, password, rootTopic)`, the order every example and this readme always used (and the order alex2node uses). 1.0.0 declared `(rootTopic, username, password)`, so a sketch written from the examples could not authenticate with the broker.
|
||
- Discovery answers go straight to MQTT (`<root>/discover_r`), one object per device, all devices at once; if the MQTT client cannot take another object (free heap under 4 KB) the rest is sent from `loop()` as the client's queue drains, for up to 5 s. 1.0.0 pushed them one per `loop()` through a 5-slot HTTP queue and silently dropped the sixth device onwards, which the backend then removed from Alexa.
|
||
- `AlexaStatusMessage::send()` returns `bool`: an oversized report (over 2047 bytes) or a full queue is reported on Serial and dropped instead of being sent truncated. `AlexaUtils::enqueue()` refuses packets that would not fit instead of truncating them.
|
||
- Discovery JSON: `semantics` is emitted only when a capability has action mappings; an `ActionMapping` directive payload is emitted as a JSON object (parsed from the text you pass) or omitted when empty, instead of the string `"{}"`.
|
||
- `Response`/`StateReport` events include the required `"payload": {}`.
|
||
- Devices and capabilities are stored in `std::deque`; pointers from `getDevice()`/`addCapability()` no longer dangle once another device or capability is added.
|
||
- The MQTT payload is copied using its length (no write past the client's buffer); fragmented directive ids are ignored (a Discover is answered on its first fragment, its payload is unused).
|
||
- `begin()` no longer prints the MQTT credentials to Serial in debug builds.
|
||
- `DirectiveReceived` no longer prints "No event registered" when the sketch has not registered it.
|
||
- Removed: `Alex2ESP::messageSplitAndSend` (declared, never defined) and `AlexaStatusMessage::setEndpointId` (did nothing).
|
||
- Reported `softwareVersion`/`firmwareVersion` in the discovery attributes are now `1.1.0`.
|
||
|
||
Examples: `WiFi.begin(WIFI_SSID, WIFI_PASSWORD)` (1.0.0 could only join open networks), `LED_BUILTIN` fallback, fixed brightness/colour-temperature Serial output, blinds use `DisplayCategory::INTERIOR_BLIND`.
|
||
|
||
Packaging: `library.json` and `library.properties` restored with the Forgejo URL and the dependencies, `LICENSE` (MIT), a real `keywords.txt`, LF line endings (`.gitattributes`), ArduinoJson 7 deprecated calls replaced (warning-free build). ESP8266 is the supported target; ESP32 is untested.
|
||
|
||
---
|
||
|
||
## Contributing
|
||
Feel free to submit pull requests or issues for feature requests and bug fixes. `pio test -e native` in the repository root runs the host tests (no board needed); they have to pass.
|
||
|
||
---
|
||
|
||
## License
|
||
This project is licensed under the MIT License. See the LICENSE file for details.
|