# Changelog All notable changes to this library are written down here. The format is that of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), the version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [2.0.0] - unreleased The library talks MQTT only, describes 28 interfaces and takes 22 KB less static RAM. The sketches of 1.x compile as they are; the readme says what changes for them (Migrating from 1.x). While it was written this version was numbered 1.2.0, which was never released. `library.json`, `library.properties` and `ALEX2ESP_VERSION` say 1.2.0 until the release. ### Added - `Alex2ESP::setServer(host, port)` before `begin()`, for a broker other than the default; the host is not copied. `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. - `AlexaDevice::onDirective(handler)`. The handler, `void handler(AlexaDirective& d)`, gets every directive of its device with the device (`d.device`), the capability it is for (`d.capability`, `d.type`), its `ns`, `name`, `instance`, `correlationToken` and `payload`, and builds the answer with `d.response()`, `d.stateReport()`, `d.error(type, message)`, `d.deferred(seconds)` or, for a scene, `d.sceneStarted()` and `d.sceneStopped()`; one function can serve several devices. `registerEvent()` stays for the handlers of 1.x, which a device calls when it has no handler of `onDirective()`. `AlexaDevice::findCapability(namespace, instance)`. - `AlexaCapability`, which `addCapability()` returns. `addCapability(AlexaInterfaces::RangeController, "Blind.Lift")` takes the instance of an interface that a device may have several of, and a device can have several capabilities of one such interface. `addFriendlyAsset()`, `setConfiguration()`, `addStateMapping()` and `setNonControllable()` are new; every setter returns the capability. `matches(namespace, instance)` tells whether a directive is for the capability. `AlexaInterface` and `AlexaActions` stay as names of `AlexaCapability` and `AlexaAction`, so a 1.x sketch compiles as it is. - New kinds of messages beside `Response` and `StateReport`: `ErrorResponse` (`AlexaErrorType` has the types of Alexa, `asErrorResponse(type, message)` turns an answer into one), `DeferredResponse` and the answer that follows it (`sendAsync()`, on `//deferredResponse`), `ChangeReport` (`AlexaDevice::changeReport(cause)`, on `/changeReport`), the events of a scene, and `DoorbellPress` (`AlexaDevice::doorbellPress()`, or `event(row, name)` for another interface, on `/event`). `AlexaDevice::response(token)` and `stateReport(token)` build an answer outside of the handler. `payload()` gives the payload of a message, `getKind()` its kind. - New report helpers, one for every property of the interfaces a device on this board is likely to have: `addColorControllerProp`, `addPowerLevelControllerProp`, `addPercentageControllerProp`, `addRangeControllerProp`, `addModeControllerProp`, `addLockControllerProp` (`AlexaLockState`), `addContactSensorProp`, `addMotionSensorProp`, `addHumiditySensorProp`, `addThermostatSetpointProp`, `addThermostatLowerSetpointProp`, `addThermostatUpperSetpointProp`, `addThermostatDualSetpointProp` and `addThermostatModeProp` (`AlexaThermostatMode`); `addProperty(row, place, value)` for a property without one. See Interface Types in the readme. - `AlexaAssets` and `AlexaUnits` in `src/AlexaResources.h`, the 103 asset ids of the catalog of Alexa as strings in program memory. `AlexaState` has the states `EcoOn`, `EcoOff`, `Low`, `Empty`, `Full`, `Done` and `Stuck` beside `Open` and `Closed`. `addStateMapping()` takes a number of any type, `addStateMapping({AlexaState::Closed}, 0)`. - `AlexaCapability::isValid()`, `false` after a setter has refused what it was given. A capability that is not valid is left out of discovery with an error; before, it was announced without what had been refused. - `AlexaInterfaceType::HUMIDITY_SENSOR` with its row, `AlexaInterfaces::HumiditySensor`. `AlexaDevice::getInterfaceType(namespace)`. - Examples as Arduino sketches, one folder per kind of device, each with its own endpoint id and a Wi-Fi connection that gives up waiting after 30 s: `Light`, `DimmableLight`, `ColorTemperatureLight`, `ColorLight`, `TemperatureSensor`, `ContactSensor`, `Blind`, `Thermostat`, `Lock`, `Scene`, `Doorbell` and `MultiDevice`. `examples/README.md` says what each does and what it takes of RAM and flash. - Host tests, `pio test -e native`: 148 tests, 52 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, the message id), 36 of discovery (the discovery object of every example of 1.x against what 1.1.0 announced, the 28 rows against the interface pages, a type without a row, the discovery objects of a range, a mode and a toggle controller, a scene and a doorbell, what a capability refuses and that it is left out afterwards, the states and the asset ids), 16 of dispatch (the device and the capability a directive reaches, one handler for two devices, the handlers of 1.x, one that answers later, a second handler for a name and one more than a device keeps, a capability more than a device holds, the answers to a directive for a capability the device lacks and to one without a handler, the log level above what the build has and the level that prints nothing), 32 of the messages (every kind against the JSON and the topic it is published with, every report helper against the JSON of its property, values outside of their range, the names of 1.x, the scale of a temperature, what `send()` and `sendAsync()` refuse) and 12 of the examples (what each announces in discovery). No board is needed. - `CHANGELOG.md`. The readme has the MQTT contract, the log lines, the memory figures and the migration from 1.x. ### Changed - Directives arrive over MQTT. The library subscribes to `/+/alexaDirective`, where Alex2MQTT has always published the whole directive, instead of fetching it over HTTP with the token from `//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 `//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 that names the device, `device not announced: ...`; 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 (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`). A level above the highest that is compiled in prints `log level 3 asked for, the lines of this build end at level 2: build with -DALEX2ESP_LOG_MAX=3`. 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 at first and doubles with every attempt, up to 60 s; it is 1 s again after a session that lasted a minute, so a session that the broker accepts and closes at once does not bring the board back every second. 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). Loss of Wi-Fi prints `Wi-Fi is down, waiting for it`, once per loss. A connect that has no answer after 30 s counts as failed and prints `disconnected: the broker did not answer within 30 s`. 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. - 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 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*`. - Interfaces are described by rows in program memory, `AlexaInterfaces::PowerController` and 27 more (see Interface Types in the readme), in place of the `switch` tables of `AlexaInterfaceUtils`, whose strings took RAM in every sketch. `addCapability()` takes a row or, as before, an `AlexaInterfaceType`; a sketch links only the rows it names. The discovery objects of the five examples of 1.x are the same byte for byte. - Versions and properties follow the interface pages of Alexa: `EndpointHealth` is announced as 3.1 (1.1.0: 3.3) and `ThermostatController` as 3.2 with its four properties (1.1.0: 3, none). `Speaker`, `StepSpeaker`, `PlaybackStateReporter`, `InventoryLevelSensor` and `WakeOnLANController` are announced as 3 (1.1.0: 1), `SimpleEventSource` as 1.0. The interfaces that 1.1.0 announced with an empty list of properties have their properties (`lockState`, `detectionState`, `mode`, `rangeValue`, `percentage`, ...). `SceneController`, `DoorbellEventSource`, `StepSpeaker` and `SimpleEventSource` are announced without a `properties` object. - `addCapability()` with a type that has no row adds nothing, prints an error and returns `nullptr`. 1.1.0 announced such an interface as version 1 without properties. - The handler of `Event` gets the type of the capability of its device that the directive names, and `AlexaInterfaceType::UNKNOWN` for a namespace the device has no capability for. 1.1.0 looked the namespace up among all interfaces. - The report helpers begin with a small letter: `addHealthProp`, `addPowerControllerProp`, `addBrightnessControllerProp`, `addColorTemperatureControllerProp`, `addToggleControllerProp(instance, state)`, `addTemperatureSensorProp(value, scale)`, `addContextProp`. The names of 1.x stay, with their order of arguments. - A helper reports a value outside of the range of its property as the nearest value of the range and prints an error; 1.1.0 sent the value as it was. The brightness and the color temperature are taken as `int` (1.1.0: `unsigned int`), so that a negative number is reported as the lowest value. - A `ChangeReport` or an event that a handler sends is not taken for the answer to its directive. - A directive that nothing answers is answered by the library with the ErrorResponse `INVALID_DIRECTIVE`: when the device has no handler for it, and when it names a capability that the device does not have and the handler sent nothing. 1.1.0 left both to the timeout, after which Alexa says that the device does not respond. A handler of `onDirective()` that sends nothing for a capability of its device prints an error; nothing is sent for it. A handler of `registerEvent()` may answer from a later `loop()`, so its silence is a line at `DEBUG`. - Devices are a linked list, and a device holds its capabilities in an array of 8 pointers (`ALEX2ESP_MAX_CAPABILITIES`); both were a `std::deque`. A ninth capability is refused with an error and `addCapability()` returns `nullptr`. `getDevice()` returns `nullptr` with an error when the heap has no room for the device. An `AlexaDevice` cannot be copied. A directive whose topic names a device of the board and whose `endpointId` does not is ignored with a line at `DEBUG` (it was an error). - A capability takes 116 bytes of heap and holds 3 friendly names, 4 action mappings and 2 state mappings (`ALEX2ESP_MAX_FRIENDLY_NAMES`, `ALEX2ESP_MAX_ACTION_MAPPINGS`, `ALEX2ESP_MAX_STATE_MAPPINGS`); one more is refused with an error. The instance and the text of a friendly name are copied, as before. The locale and the directive name and payload of an `ActionMapping` are kept as pointers, where 1.1.0 copied them: pass literals. An `ActionMapping` announces its actions in the order of `AlexaAction`. - What Alexa does not accept is refused with an error that says what to change. `addCapability(row)` for an interface with instances and no instance, or with an instance for an interface without, adds nothing and returns `nullptr`. A capability with instances that has no instance or no friendly name when the device is announced (a 1.x sketch sets both after `addCapability(type)`) is left out of discovery. Action and state mappings are taken by `RangeController`, `ModeController` and `ToggleController` only. - `PlaybackController` and `WakeOnLANController` are announced with `"properties": {}`, as their pages show them. - The examples of 1.x are in `examples/legacy/`, one sketch folder each. `lightWithBrightness` and `lightWithColorTemp` act on `AdjustBrightness` (clamped to 0 to 100); `lightWithColorTemp` starts at 2700 K, where it reported 0 K, and acts on `IncreaseColorTemperature` and `DecreaseColorTemperature` in steps of 500 K. - The library has its version in one place, `ALEX2ESP_VERSION` in `src/AlexaVersion.h`; the devices report it in discovery as `softwareVersion` and `firmwareVersion`. The `platformio.ini` of the repository is for the host tests; a sketch does not need 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`. - `AlexaInterfaceType::AUTHORIZATION_CONTROLLER` and `AUTOMOTIVE_VEHICLE_DATA`, which Alexa has withdrawn. `AlexaInterfaceUtils` (`toString`, `fromString`, `getVersion`, `getProps`): a row has the namespace, the version and the properties, `alexaInterfaceRow(type)` gives the row of a type. The constructor of `AlexaInterface` takes a row. `DisplayCategory` and `AlexaInterfaceType` are one byte wide. - `AlexaInterface::getJSON()` (`toJson()` adds the capability to the capabilities of its endpoint) and `getProps()` (the row has the properties), `ActionMapping::getJSON()` and the `String` and `std::vector` members of `ActionMapping`, the class `FriendlyName`. - The HTTP fallback: the library makes no HTTP requests. Boards that run 1.1.0 are not affected: the backend keeps the token topic and the HTTP routes. ### Fixed - A doorbell is announced with `"proactivelyReported": true` and a scene with `"supportsDeactivation"`, beside the name of the interface as their pages have it. `setSupportsDeactivation(true)` is for a scene that can be undone. Without the first, Alexa accepted the discovery, did not list the doorbell and answered its `DoorbellPress` with an error (seen with a board on 2026-09-28). - A temperature is reported in the scale it is given in: `AddTemperatureSensorProp(TemperatureSensorScale::FAHRENHEIT, 69)` reports 69 `FAHRENHEIT`, where 1.1.0 reported 20.56 `CELSIUS`. `TemperatureSensorScale::KELVIN` is new. - `messageId` is a UUID of version 4 from the random source of the hardware. 1.1.0 sent 37 characters from `rand()`, seeded with the time in seconds: two messages of one second had the same id. - `registerEvent()` with a name that has a handler replaces the handler; 1.1.0 kept the first and never called the second. An eleventh name (`MAX_EVENTS` is 10), and a call without a name or a function, is refused with an error; 1.1.0 dropped the eleventh without a word. ### Memory `examples/legacy/basicLight` for a D1 mini takes 30,520 bytes of static RAM (1.1.0: 52,768) and 333,989 bytes of flash (1.1.0: 350,885), as PlatformIO reports them (espressif8266 4.2.1, Arduino core 3.1.2). 18,260 bytes of the static RAM were the five 2 KB queue slots, three more 2 KB buffers and the two HTTP clients; 3,720 were the names, versions and properties of all interfaces and the names of the display categories, which are in flash now. SNTP and the time stamp are 1.8 KB of the flash figure. The instance, names, configuration and mappings of a capability are 2.0 KB of it, the directive handler and the ErrorResponse 0.9 KB, the kinds of messages and the UUID 1.3 KB. A kind that a sketch does not send and a report helper that it does not call are not linked. With `-DALEX2ESP_LOG_MAX=0` the sketch takes 30,496 bytes of static RAM and 327,953 of flash, with `-DALEX2ESP_LOG_MAX=3` 30,520 and 334,477. The sketches of 2.0 for a D1 mini take 30,572 (`TemperatureSensor`) to 31,044 bytes (`Thermostat`) of static RAM and 332,705 (`Light`) to 338,797 bytes (`ColorLight`) of flash; the readme has the table. ## [1.1.0] - 2026-09-28 ### Changed - `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 (`/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 `"{}"`. - 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`. ### Fixed - `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). ### Added - 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. ## [1.0.0] - 2024-12-24 The first version, as it was in the repository from 2024-12-18 to 2024-12-24; it has no tag. ### Added - The client `Alex2ESP`, `AlexaDevice`, `AlexaInterface` with friendly names and action mappings, discovery and directives through Alex2MQTT (the token over MQTT, the directive and the reports over HTTP). - Report helpers for `EndpointHealth`, `PowerController`, `BrightnessController`, `TemperatureSensor`, `ColorTemperatureController` and `ToggleController`; `AddContextProp` for any other property. - The examples `basicLight`, `lightWithBrightness`, `lightWithColorTemp`, `tempSensor` and `blindControl`.