261 lines
16 KiB
Markdown
261 lines
16 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
|
||
|
||
- **Discovery.** On `<root>/discover` the library answers with one discovery object per device, published straight to `<root>/discover_r` over MQTT (no HTTP round trip, no queue slot). 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 the moment the request arrives. Each object sits on the heap (about 1 KB) until the broker acknowledges it; when the MQTT client refuses another one (free heap under 4 KB), the library prints `[Alex2ESP] discovery publish deferred at <endpointId>` and sends the rest from `loop()` as the queue drains, for up to 5 s after the request. `[Alex2ESP] 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.** A directive arrives as a short id on `<root>/<endpointId>/alexaDirective_e`; the library fetches the full directive over HTTP, fires `ReportState` or `Event` (and `DirectiveReceived`, if registered), and your status report is queued for an HTTP POST that the backend republishes on `<root>/<endpointId>/alexaResponce`, replacing `{REPLACE_WITH_DATETIME}` with the current time.
|
||
- **Limits.** The send queue holds 5 reports of up to 2047 bytes each. `send()` returns `false` and prints a `[Alex2ESP]` line on Serial when a report does not fit or the queue is full; nothing is ever sent truncated. Debug logging of the library's internals is compiled in by defining `Alex2ESP_DEBUG` in `AlexaUtils.cpp`; credentials are never printed.
|
||
|
||
---
|
||
|
||
## 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["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; // Alex2MQTT server wil do the replace
|
||
doc["uncertaintyInMilliseconds"] = 0;
|
||
AddContextProp(doc.as<JsonObject>())
|
||
```
|
||
|
||
---
|
||
|
||
## Changelog
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
## License
|
||
This project is licensed under the MIT License. See the LICENSE file for details.
|