Commit graph

32 commits

Author SHA1 Message Date
f9c787a67a Sizes of the examples measured again after the doorbell and scene change
All 17 sketches rebuilt for d1_mini at 50467ce, 0 warnings. Static RAM is
unchanged; flash grew by 140 to 352 bytes per sketch (Light 332,705, was
332,565). The tables of the readme and of examples/README.md and the line
of the changelog carry the new numbers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 22:38:57 +00:00
50467ced28 Changelog and test counts for the doorbell and scene announcement
The entry under Fixed of 2.0.0, and 148 host tests (36 of discovery) in the
readme and the changelog. They belong to c3d02f4, whose edit of these two
files did not apply.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 22:20:52 +00:00
c3d02f462e Announce a doorbell with proactivelyReported and a scene with supportsDeactivation
An interface without properties says beside its name what Alexa has to know
of it; the library left both out. With a board and Alexa on 2026-09-28 the
discovery of a doorbell was accepted (202), the endpoint was not listed, and
its DoorbellPress was answered with 500 INTERNAL_SERVICE_EXCEPTION.

DoorbellEventSource now always carries "proactivelyReported": true.
SceneController carries "supportsDeactivation", false until the sketch calls
setSupportsDeactivation(true); on another interface the call is refused and
logged. The Scene example says that it can be undone.

148 host tests. Light: static RAM 30,616 B, flash 332,705 B (+140 B).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 22:20:34 +00:00
1b8a5ff806 readme for 2.0: what it needs, quick starts, the interface table, the MQTT contract, log lines, memory, migration from 1.x
Rewritten along section 6a of the design. The Light example is in the
readme in full; the 13 other code blocks are excerpts of the examples and
of test/readme/Configuration/Configuration.ino, a sketch that exists to
compile the calls of the Configuration section (setServer, setTimeSource,
setLogLevel, AlexaLog::setOutput). The interface table says which six
interfaces Alexa drove from this library on 2026-09-28 and that the rest
is not tried. ESP32: never compiled. The changelog is in CHANGELOG.md.
Built for d1_mini: 17 examples and the Configuration sketch, 0 warnings;
147 host tests pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 22:13:44 +00:00
69b4d59896 Examples as Arduino sketches: one folder per device type, each its own endpointId, Wi-Fi with a timeout
Light, DimmableLight, ColorTemperatureLight, ColorLight, TemperatureSensor, ContactSensor, Blind,
Thermostat, Lock, Scene, Doorbell and MultiDevice on the 2.0 API: addCapability(row, instance), one
onDirective() handler, the report helpers, ChangeReports from loop(), a deferred answer for the lock.
Wi-Fi is waited for 30 s, then the sketch says what to check and carries on.

examples/README.md has what each sketch does, its limits and the measured sizes for d1_mini: Light
takes 30,616 bytes of static RAM and 332,565 of flash; all 17 sketches build with 0 warnings.
test/test_examples compares the discovery object of every new sketch: 147 host tests (were 135).
The sketches were compiled, not run on a board.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 21:41:51 +00:00
7369883479 Examples of 1.x move to examples/legacy, one sketch folder each
basicLight, lightWithBrightness, lightWithColorTemp, tempSensor and blindControl become
examples/legacy/<name>/<name>.ino, which the Arduino IDE and PlatformIO both open. Their content is
unchanged: they keep proving that a 1.x sketch compiles against 2.0, and their discovery tests
(test/test_discovery) are untouched.

Built for d1_mini from the sketch folders, 0 warnings each; basicLight takes 30,520 bytes of static
RAM and 333,989 of flash, the same as before the move. library.json and the readme name the new
place.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 21:40:25 +00:00
6e15226ce0 Log: one line for whatever is dropped, a level the build lacks is reported, no false alarm for a 1.x handler
Audit of the paths that drop or refuse something. registerEvent() dropped an eleventh
handler and a second one for the same name without a word: the name now gets the new
handler, an eleventh name and a call without a name or a function print an error. A
device that cannot be announced printed two lines, one without its id; it prints one.
setLogLevel() above ALEX2ESP_LOG_MAX prints an error that names the build flag.
"the handler sent no answer" is an error for a handler of onDirective() only: a 1.x
handler may answer from a later loop(), its silence is a line at DEBUG.
getDevice() returning nullptr and printMemoryInfo() are documented; keywords.txt has
the 2.0 names and loses three that were removed.
basicLight: static RAM 30,520 B, flash 333,989 B (+600); ALEX2ESP_LOG_MAX=0 30,496 /
327,953, =3 30,520 / 334,477; five examples, log0 and log3 build with 0 warnings.
Host tests: 135 (dispatch 11 -> 16).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 21:10:19 +00:00
77156ffbe5 Report helpers for every tier-1 property, asset ids, AlexaCapability::isValid()
AlexaStatusMessage gets a helper for color, percentage, power level, range, mode, lock state, contact,
motion, humidity and the thermostat (target, lower and upper setpoint, mode), and addProperty(row, place,
value) for a property without one. Each is a function of its own on one shared builder; a value outside of
the range of its property is reported as the nearest of the range with an error.
AlexaResources.h holds the 103 asset ids of resources-and-assets.html (AlexaAssets, AlexaUnits); AlexaState
has the seven states that were missing; addStateMapping() takes a number of any type.
A setter that refuses its argument makes isValid() false, and the capability is left out of discovery with
an error instead of being announced without what was refused.
basicLight: static RAM 30,520 B (was 30,540), flash 333,389 B (was 334,981); 130 host tests (was 112).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 21:00:01 +00:00
3215457f91 AlexaStatusMessage: DeferredResponse, ChangeReport, ErrorResponse, scene and doorbell events, UUID-shaped messageId
A message has a kind, and the kind decides its topic: answers on
<root>/<id>/alexaResponce, the answer after a DeferredResponse on
<root>/<id>/deferredResponse (sendAsync()), a ChangeReport on
<root>/changeReport with the changed properties apart from the others
(context()), DoorbellPress on <root>/event. AlexaDirective gains deferred(),
error(type, message), sceneStarted() and sceneStopped(); thermostat error
types go out in the namespace of the thermostat. The helpers are add*Prop,
the Add*Prop names stay; a temperature keeps its scale (69 FAHRENHEIT, was
20.56 CELSIUS). messageId is a version 4 UUID from the hardware random
source, where rand() seeded per second gave two messages one id.
basicLight: static RAM 30,540 B (-76), flash 334,981 B (+1,280);
112 host tests (bridge logic 52, messages 19, new).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 20:35:56 +00:00
42af413a6e Reconnect: keep the wait across short sessions, name a timeout and a lost link, setServer()
The wait between attempts started at 1 s again when the broker accepted the
session, so a session that was closed right after it opened (two boards with
one client id) came back every 2 s without end. It now starts again only
after a session that lasted 60 s; across shorter ones it keeps doubling.
An attempt given up after 30 s prints "the broker did not answer within
30 s", and loss of Wi-Fi prints "Wi-Fi is down, waiting for it" once per loss.
setServer(host, port) before begin() names another broker; the host is not
copied. basicLight: static RAM 30,616 B (+16), flash 333,701 B (+264);
91 host tests (bridge logic 50).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 20:24:51 +00:00
d3e7ee7f2a AlexaDevice: capability array, linked list, a directive handler with the device in hand
onDirective(handler) registers void handler(AlexaDirective&): the directive comes with its device, the
capability it is for, namespace, name, instance, correlationToken and payload, and response() and
stateReport() build the answer, so one function serves several devices. registerEvent() is kept; the
five examples compile unchanged and announce the same discovery objects.
Devices are a linked list on the bridge and a device holds ALEX2ESP_MAX_CAPABILITIES (8) capability
pointers in place of two std::deque; one capability more is refused with an error.
A device without a handler, and a handler that sends nothing for a capability the device lacks, are
answered with the ErrorResponse INVALID_DIRECTIVE instead of a timeout. A directive whose endpointId is
not a device of the sketch is ignored at DEBUG.
basicLight on a D1 mini: static RAM 30,672 -> 30,600 B, flash 332,541 -> 333,437 B, 0 warnings in the five
examples; 88 host tests (11 new, test/test_dispatch).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 19:45:57 +00:00
7afc4346e8 AlexaCapability: instance, asset and text names, configuration, action and state mappings, nonControllable
AlexaCapability replaces the header-only AlexaInterface as what a device holds: 116 bytes with fixed places for
3 names, 4 action mappings and 2 state mappings, no std::vector or std::string. AlexaInterface and AlexaActions
stay as 1.x names; the five examples compile unchanged and announce the same bytes.
addCapability(row, instance) adds several capabilities of one generic controller. An instanced interface without
an instance or a name, an instance or semantics on an interface that takes none, and a full store are refused
with an ERROR line that says what to change.
PlaybackController and WakeOnLANController announce "properties": {} (AIF_EMPTY_PROPERTIES).
basicLight on a D1 mini: static RAM 30,788 -> 30,672 B, flash 330,509 -> 332,541 B; host tests 61 -> 77.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 19:33:57 +00:00
8b11cc2394 Describe interfaces with PROGMEM descriptor rows; drop the switch tables
AlexaInterfaces.h/.cpp hold one row per interface in program memory (28: namespace,
version, property names, flags, type), each row and string an object of its own, so
the linker keeps only the rows a sketch names. addCapability(AlexaInterfaceType) is
an always-inline switch that folds to one row; a type without a row is refused with
an error. The Event handler gets its type from the capabilities of the device.
Versions follow the interface pages (EndpointHealth 3.1, ThermostatController 3.2),
HumiditySensor is new, the two withdrawn interfaces and AlexaInterfaceUtils are gone.
Display category names are in flash. The discovery objects of the five examples are
unchanged: 14 new host tests compare them with what the previous commit announced.
basicLight on a D1 mini: static RAM 34,508 -> 30,788, flash 338,533 -> 330,509;
it links two rows, EndpointHealth and PowerController. Host tests: 47 -> 61.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 19:04:14 +00:00
d534f23f27 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>
2026-09-28 18:45:18 +00:00
2ed52c8721 Reconnect with back-off on every disconnect reason and log it
The session is opened again whatever AsyncMqttClient gives as the reason, after 1 s doubling
to 60 s (AlexaReconnectBackoff, reset when a session opens), and only while Wi-Fi is up.
1.1.0 retried a lost TCP connection every 5 s and nothing else, silently: a refused password
or a broker that was restarting left the board offline until a reset.
Every disconnect prints its reason in words and the wait before the next attempt. The first
connect waits for Wi-Fi; a connect without an answer after 30 s counts as failed, because the
MQTT client reports nothing when no TCP connection was made. Keep-alive 30 s.
basicLight: RAM 34,452 -> 34,476 B, flash 337,461 -> 338,281 B; host tests 41 -> 45.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 18:43:00 +00:00
cb5c9876fe Version 1.2.0 in library.json, library.properties and the discovery attributes
After 8ea7778 the readme described the behaviour as 1.2.0 while the package still said 1.1.0: PlatformIO listed "Alex2ESP @ 1.1.0" in the dependency graph, and the devices reported softwareVersion and firmwareVersion 1.1.0 to Alexa.

The choice made here: the number changes now, with the first commits of the 1.2.0 work, and not in the commit that closes the checkpoint. From this commit on the readme, the package files and the code agree at every commit of the branch, and nothing has to be remembered before the tag. The branch is not a release until it is tagged.

The code had the number twice, as the softwareVersion member of AlexaDevice and as a literal for firmwareVersion. Both now use ALEX2ESP_VERSION from the new src/AlexaVersion.h, so a release changes three places: that header, library.json and library.properties. The readme's hint for pinning a release keeps v1.1.0, the newest tag that exists.

For a sketch: getSoftwareVersion() and the discovery attributes say 1.2.0.

examples/basicLight.cpp for d1_mini, static RAM / flash in bytes: 34,452 / 337,461 -> 34,452 / 337,461, no warnings; the dependency graph of the build says "Alex2ESP @ 1.2.0".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 16:02:52 +00:00
d7d0e903a6 readme: the 1.2.0 changelog names the transport argument of AlexaDevice and AlexaStatusMessage, AlexaTransport, publish() and timestamp()
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>
2026-09-28 16:01:53 +00:00
5b090a43dd 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>
2026-09-28 16:01:25 +00:00
39717d5287 Report an unset clock when the session opens and once a minute while reports are sent
When no NTP server answers (DNS for the pool fails, outbound UDP port 123 is blocked) the bridge connects after 5 s and every report carries a timeOfSample in 1970 for as long as the board runs. The only trace was one line at boot. 1.1.0 did not need the time of day: the HTTP route of the backend stamped the reports.

Alex2ESP::timestamp() now checks the clock it reads. While it is not set, the line that says so is printed when the session opens and then at most once per 60 s while reports are built; timestamp() runs once per property, so the line is limited by time and not per call. The line names what to check: "the clock is not set, reports carry a time in 1970: no answer from pool.ntp.org or time.nist.gov (DNS, outbound UDP port 123)", or, after setTimeSource(false), that the clock is left to the sketch. It replaces "clock not set after 5000 ms: ...".

For a sketch: the reports are sent as before, with the time the clock has. The readme says in its Session paragraph that the board has to reach an NTP server.

Measured with the bridge built for the host against a fake MQTT client (not in the repository), clock never set, a report of two properties every 10 s for 300 s: 30 reports sent, the line printed once at the connect and 5 times in the 300 s; with the clock set, not at all.

Not checked here: what Alexa does with a report stamped 1970. That needs the board (TEST-PLAN 3.1, last row).

examples/basicLight.cpp for d1_mini, static RAM / flash in bytes: 34,444 / 337,145 -> 34,452 / 337,401, no warnings. 41 host tests pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:58:49 +00:00
6fe8311a6f Queue up to eight directives for loop(); drop the repeat of a directive while it arrives
8ea7778 kept one directive for loop() and recognised the repeat that the broker mirror delivers in two ways: bytes compared with the directive that still waited, and the messageId once loop() had parsed it. A repeat that arrived after its directive had been read therefore took the one place until the next loop(), and a different directive behind it was dropped. Alexa sends a group command as one directive per endpoint, so a board with several endpoints lost directives whenever they arrived faster than loop() ran; 1.1.0 queued five tokens.

AlexaDirectiveBuffer is now a queue. The arriving message is collected in a heap block of its own and hashed (FNV-1a, 64 bit) as its fragments come in. At the last fragment it is one of three things: a repeat, when the hash is among the last 16 that were queued, and then it is freed and never takes a place; a directive, which is queued; or lost, when eight directives (ALEX2ESP_MAX_QUEUED_DIRECTIVES) or 8188 bytes (ALEX2ESP_MAX_QUEUED_BYTES, four times the largest directive) already wait. Whether there is a place is decided at the last fragment, because loop() may have read a directive by then. A message for which there is no memory is still hashed, so its loss is reported only when it is not a repeat. loop() parses one directive per call, in the order of arrival, and frees its block before the handler runs.

The messageId check stays for a directive that comes again with other bytes; it remembers 16 ids instead of 4 and shares the ring with the buffer (AlexaRecentHashes). The two limits are in src/AlexaLimits.h, with #error for values that cannot work. The constructor of the buffer takes the allocator, malloc by default, so the tests can let it fail.

For a sketch: nothing to change. The error line of a directive that finds no place reads "directive of N bytes on <topic> dropped: 8 directives, M bytes, already wait for loop()". The heap holds up to 8188 bytes of waiting directives and one arriving directive of up to 2048, where it held one directive.

Measured with the bridge built for the host against a fake MQTT client (not in the repository), directives of 793 bytes, every message delivered twice, 8ea7778 -> this commit:
  repeat of D1 and a new D2 after D1 was handled   D2 dropped -> D2 handled
  group of 5 in one burst                          1 of 5 handled, 8 error lines -> 5 of 5, none
  group of 8 in one burst                          8 of 8 handled, no error line
  group of 10 in one burst, not mirrored           8 of 10 handled, 2 error lines
  group of 10, a loop() after every fourth message 10 of 10 handled, no error line

Tests: 41 host tests (33 before). New: the order of the queue and the reuse of its places, the ninth directive, a place that becomes free while a directive arrives, the limit in bytes, the largest directive in an empty queue, the repeat of a waiting directive and of one that was read, a group of five with repeats, a repeat when no place is free, a directive that differs in one byte, how long a repeat is remembered, a directive and a repeat without memory, the FNV-1a test vectors. They also pass under -fsanitize=address,undefined. Seven faults planted in a copy of AlexaBridgeLogic.cpp (no repeat check, no limit in bytes, no limit in places, no bounds check, a lost directive remembered, release() that keeps the bytes, last in first out) were each noticed: six by failing tests, the missing bounds check by AddressSanitizer as a heap-buffer-overflow.

Built for d1_mini with empty credentials (PlatformIO 6.2.0, espressif8266 4.2.1), static RAM / flash in bytes, e48f858 -> this commit, no warnings:
  basicLight           34,116 / 336,757 -> 34,444 / 337,145
  lightWithBrightness  34,232 / 340,517 -> 34,560 / 340,921
  lightWithColorTemp   34,380 / 341,177 -> 34,708 / 341,581
  tempSensor           34,024 / 335,457 -> 34,352 / 335,845
  blindControl         34,256 / 338,925 -> 34,584 / 339,313
The 328 bytes of RAM are the two rings of 16 hashes (256) and the eight places of the queue.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:57:03 +00:00
e48f8580f9 Leave room for the answer: ALEX2ESP_MAX_MESSAGE defaults to ALEX2ESP_MAX_DIRECTIVE + 1024
Both limits were 2047 bytes. An answer repeats the correlationToken of its directive, which is most of a large directive, and adds 140 to 170 bytes for every property. So a directive between about 1,750 and 2,047 bytes was accepted, the sketch acted on it, and the answer was refused: the lamp switched and Alexa reported a device that does not respond.

The limit for messages now follows the limit for directives unless it is set: 2047 + 1024 = 3071 bytes, room for six properties on top of the largest directive. Both defines moved to src/AlexaLimits.h, next to each other, with the relation in the comment. The limit also applies to the discovery object of a device, which may now be 3071 bytes.

For a sketch: a report between 2,048 and 3,071 bytes is sent, where it was refused with an error. A project that sets ALEX2ESP_MAX_MESSAGE keeps its value.

Measured with the bridge built for the host against a fake MQTT client (not in the repository), a TurnOn of 2,047 bytes answered with two properties:
  8ea7778      handler ran, send() false, "2374 bytes not sent, the limit is 2047"
  this commit  handler ran, send() true, 2,374 bytes published

Tests: test_largest_directive_can_be_answered puts a directive of ALEX2ESP_MAX_DIRECTIVE bytes through the receive buffer, builds the Response of a lamp to it and checks it against ALEX2ESP_MAX_MESSAGE, also with six properties. 33 host tests pass.

examples/basicLight.cpp for d1_mini, static RAM / flash in bytes: 34,116 / 336,757 -> 34,116 / 336,757, no warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:38:46 +00:00
46a27c26e9 AlexaLog: compile in projects that define DEBUG, ERROR or INFO as macros
8ea7778 introduced AlexaLogLevel::NONE, ERROR, INFO and DEBUG. A sketch that starts with "#define DEBUG 1", or a project built with -DDEBUG, did not compile any more: "expected identifier before numeric constant" at the enumerator, and with the build flag also in the library's own files, because ALEX2ESP_LOGD spelled AlexaLogLevel::DEBUG wherever it expanded. The same sketch builds against 1.1.0.

AlexaLog.h declares the enumerators between #pragma push_macro / #undef and #pragma pop_macro of the four names: a macro of the sketch is set aside for the declaration and is back after it. ALEX2ESP_LOG takes the level as its number and casts it, so no call site names an enumerator. AlexaLog.cpp, the one file that names the levels, undefines the four names after its includes.

For a sketch: nothing changes unless it defines such a macro. One that does cannot write AlexaLogLevel::DEBUG, because the preprocessor replaces the name; setLogLevel(static_cast<AlexaLogLevel>(3)) selects the same level. NONE is set aside in AlexaLog.h as well, but a project that defines NONE fails for another reason: AsyncMqttClient has an enumerator NONE (ParsingInformation.hpp).

Built for d1_mini with empty credentials (PlatformIO 6.2.0, espressif8266 4.2.1), examples/basicLight.cpp, static RAM / flash in bytes, 8ea7778 -> this commit, no warnings:
  as committed                                    34,116 / 336,757 -> 34,116 / 336,757
  "#define DEBUG 1" as the first line             does not compile -> 34,116 / 336,757
  DEBUG, ERROR and INFO defined in front          34,116 / 336,757
  build_flags = -DDEBUG                           does not compile (28 errors) -> 34,116 / 336,757
  build_flags = -DDEBUG -DERROR -DINFO            34,116 / 336,757
The other four examples are unchanged as well (34,232 / 340,517, 34,380 / 341,177, 34,024 / 335,457, 34,256 / 338,925).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:30:46 +00:00
8ea777806a Drop the HTTP fallback: directives arrive on <root>/<id>/alexaDirective and reports leave over MQTT
The library fetched every directive over HTTP (GET /Alex2ESP/<token>, the token taken from <root>/<id>/alexaDirective_e) and posted every report back over HTTP. The detour was built in 2024 because AsyncMqttClient hands a message larger than one TCP segment to onMessage in fragments. Alex2MQTT publishes the whole directive on <root>/<id>/alexaDirective, so the fragments are reassembled instead: by index/total, into one heap block of total + 1 bytes that is freed as soon as loop() has parsed it.

Removed: ESP8266HTTPClient with processHttpGet/processHttpPost and their two HTTPClient and two WiFiClient members; the 5-slot packet, topic and receive queues, combinedData, receivePayload and AlexaStatusMessage::outputString (17,264 bytes of .bss); the {MESSAGE_PAYLOAD_SPLIT} and {REPLACE_WITH_DATETIME} conventions; the subscription to the _e token topic; AlexaUtils.cpp (AlexaUtils::printMemoryInfo stays, in the header).

Receive: the bridge subscribes <root>/discover and <root>/+/alexaDirective. The MQTT callback only collects; loop() parses and dispatches, and answers discovery. A directive over ALEX2ESP_MAX_DIRECTIVE (2047) is refused. One directive is in flight: a second one that arrives before loop() ran is dropped. Both are logged. A byte-identical repeat of the waiting directive and a repeat of one of the last four messageIds (kept as 64-bit hashes, 32 bytes) are ignored, because the broker mirror delivers every message twice. The wildcard subscription also delivers the directives of other boards of the account; they are recognised by their topic before anything is allocated.

Publish: measureJson first. A message is refused when it is over ALEX2ESP_MAX_MESSAGE (2047), when its document overflowed, when there is no session, when the packet does not fit the largest free block, or when AsyncMqttClient returns 0. Every refusal is logged and returned as an AlexaSendResult; AlexaStatusMessage::send() keeps its bool. The limit now also applies to the discovery object of a device.

Time: timeOfSample is an ISO 8601 UTC instant from the clock of the board (gmtime_r + snprintf, no strftime). begin() calls configTime(0, 0, "pool.ntp.org", "time.nist.gov") and loop() holds the first connect until the clock is set or 5 s have passed. setTimeSource(false) leaves the clock to the sketch. AddContextProp() fills timeOfSample in when a hand-built property has none or carries the old placeholder.

Log: AlexaLog, with a level at run time (setLogLevel) and a ceiling at build time (ALEX2ESP_LOG_MAX), replaces the Serial prints and the Alex2ESP_DEBUG define; the new receive and publish paths need a line for every refusal. The log and the time stamp call vsnprintf/snprintf with a PROGMEM format and not the _P variants: newlib keeps those in one object with printf_P and sprintf_P, which links the FILE-based printf. Measured on basicLight: 5,776 bytes of flash.

For a sketch: the 1.x API is unchanged, the five examples compile unmodified and without warnings. loop() no longer blocks for two HTTP round trips per directive. send() publishes at once and returns false without a session, where 1.1.0 queued the report. The MQTT session opens from loop(), up to 5 s after begin(). getState() is CONNECTED once both subscriptions are acknowledged. The 1.2.0 section of readme.md lists every change; its transport section is rewritten.

Measured for d1_mini with empty credentials (PlatformIO 6.2.0, espressif8266 4.2.1, Arduino core 3.1.2), static RAM / flash in bytes, 1.1.0 -> this commit:
  basicLight           52,768 / 350,885 -> 34,116 / 336,757
  lightWithBrightness  52,880 / 354,729 -> 34,232 / 340,517
  lightWithColorTemp   53,028 / 355,389 -> 34,380 / 341,177
  tempSensor           52,676 / 349,441 -> 34,024 / 335,457
  blindControl         52,900 / 353,069 -> 34,256 / 338,925
basicLight with -DALEX2ESP_LOG_MAX=0: 34,108 / 333,289. SNTP and the time stamp are 1,848 bytes of the flash figure.

Tests: platformio.ini with [env:native] and test/test_bridge_logic, 32 host tests of the logic in src/AlexaBridgeLogic.cpp (reassembly at every fragment size, both limits, repeats, publish checks, topics, time stamps). They also pass under -fsanitize=address,undefined. .gitignore no longer hides /test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 14:31:57 +00:00
87caad25be 1.1.0: begin(username, password, rootTopic) - the library signature now matches every example, the readme and the website (as published since 2024 no sketch could authenticate: the 64-hex password became the MQTT username). Discovery answers go out over MQTT directly, every device, resumed from loop() under heap pressure within the backend's 5 s window, instead of the 5-slot HTTP queue that silently dropped the rest (which the backend's reconcile then deleted from Alexa). JSON aligned with Alexa / alex2node: event.payload present, ActionMapping payload an object, semantics only when a capability has mappings. Pointer-stable device/capability containers (std::deque), the MQTT payload is copied by length before parsing (no write past the buffer), fragmented directive ids are ignored (a Discover is answered on its first fragment), deprecated ArduinoJson 7 calls replaced, credential Serial prints removed, ESP32 include guards (untested; the ESP8266 is the target). Examples: WIFI_PASSWORD, LED_BUILTIN fallback, string+int print fixes. library.json + library.properties restored (1.1.0; AsyncMqttClient, ArduinoJson 7, ESP Async TCP), LICENSE (MIT), a real keywords.txt, .gitattributes text=auto eol=lf (tree normalised to LF). readme: Forgejo URLs, platformio.ini snippet, Library Manager install, begin() order, INTERIOR_BLIND for blinds, 1.1.0 changelog. All five examples compile warning-free for d1_mini (PlatformIO 6.2, espressif8266) on pve-B450.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 04:58:38 +00:00
David
1313f47856 Add examples (brightness, color temp, temp sensor, blind control) + AlexaStatusMessage; update src/keywords/readme; drop library.json/properties 2024-12-24 12:00:00 +00:00
chaos511
9bf63a4146 Merge branch 'main' of https://github.com/chaos511/Alex2ESP 2024-12-23 20:38:40 -06:00
chaos511
f6691368f1 Add support for COLOR_TEMPERATURE_CONTROLLER 2024-12-23 20:38:25 -06:00
David
cda54f2948
Update readme.md 2024-12-18 23:57:57 -06:00
chaos511
6d1c7a3e12 update README 2024-12-18 23:47:33 -06:00
chaos511
8a1d862d56 Add support for TEMPERATURE_SENSOR 2024-12-18 23:46:38 -06:00
chaos511
7f5357dadf Add support for BRIGHTNESS_CONTROLLER 2024-12-18 20:51:35 -06:00
chaos511
85d59985d2 initial release 2024-12-18 16:44:06 -06:00