The read timeout was kept in 16 bits, so a longer one wrapped: Berry's
webclient.set_timeouts(80000) gave up after about 14.5 s, and 70000
after 4.5 s. Keep it in 32 bits, in HttpClientLight and
AsyncHttpClientLight, as ESPEasy's copy of the client now does.
Matter_TLV.tlv2raw() wrote a UTF2 string with bytes().frostring(), a
typo for fromstring(), so encoding any string of more than 255 bytes
(which is given type UTF2 automatically) raised attribute_error.
\#Description:
TuyaSendCmd() doesn't have any sort of pacing, so two back-to-back
commands can land within milliseconds of eachother.
In my specific devices and likely many other Tuya based devices, this
can cause the MCU to crash. This is compounded by the fact that Tasmota
(unnecessarily) writes the local-time every 60s, and the heartbeat every
11s. So roughly every 660s, they coincide, and then several times a day
the MCU will crash.
The end result is that my lights would randomly turn-off, or in the
case of my 3-way lights, they would even turn on randomly in the middle
of the night.
This patches the Tuya MCU driver to queue outgoing frames and release them from FUNC_LOOP once the following four spacing rules pass:
byte_counter must not be part way through receiving a frame
tx_ack_wait still waiting on a reply, given up after
TUYA_TX_ACK_TIMEOUT_MS
tx_last_ms minimum gap, for commands that don't receive a response
rx_last_ms silence since the last byte in
TUYA_TX_MAX_HOLD_MS limits the ack and quiet wait time.
Behaviour changes for existing users:
- datapoint writes are paced, so a fade or slider drag (might) emit fewer
intermediate values. The queue insertion order is such though that
values are never lost, and this is quite the edge-case.
- a command can be delayed by the minimum gap plus the hold ceiling
- a full queue with nothing collapsible drops its oldest frame
\#Testing:
I wrote a stress test that could crash the MCU at-will by spamming lots
of writes heartbeat writes.
With this applied, the same test does not crash the MCU
I have also been using this for over a month on all of my lights with 0
issues.
WifiManagerBegin() turned the AP on with WifiSetMode() and named it with
WiFi.softAP() only after WifiSetMode()'s delay(100): for that time the AP
beaconed under the SDK's default name, ESP_ and the AP MAC, which a phone
or computer then lists beside the real name, often for a minute after.
WiFi.softAP() turns the AP on and names it at once, so it now comes first;
WifiSetMode() then sets the station side as before.
* Add BLE timeout and eQ3 TRV enhancements
- Add a configurable BLE connection timeout and default it to 15 seconds (`BLEConnTimeout`)
- Improve eQ3 device detection
- Harden eQ3 advertising parsing
- Request and report TRV firmware version
- Add an option to disable the use of aliases in eQ3 MQTT topics (`TRVAliasAsTopic`)
* Comment MAC prefix
wc_errorCodeMessage() passed httpCode as well as the elapsed time to the
"Connection timeout after %d ms" format, so the log showed the code:
"HTP: Connection timeout after -1 ms".
The Modbus RTU slave was polled from FUNC_LOOP, so anything that blocks
the main loop (WiFi or MQTT reconnect, flash writes) silenced it and a
PLC master saw no-response timeouts and, once the stale requests were
served, mismatching or unqueried slave answers.
The RS485 port is now served by a FreeRTOS task. Reads are answered
from a register snapshot that the main loop refreshes every few
milliseconds and after every write. Writes are queued to the main loop,
which runs the same miel_hvac_apply_* setters as before, and the task
returns the real result or an acknowledgement if the main loop is
stalled for more than 150 ms. A snapshot older than 30 s answers with
exception 04.
run_tests.be and button_stability_tests.be stop with assert_failed right
after "Matter epoch conversion for UTC and local time: OK" on development.
Three test-only mistakes, each hiding the next:
- run_tests.be checked the RSSI item's stored TLV type before encoding.
"Clamp RSSI" (#25126) stores signed values as I4; the encoded bytes are
still the 1-byte 00C3, which the next assert checks. Check the value only.
- TestGarageDevice (run_tests.be) lacks disable_bridge_mode, which
Matter_Plugin_Device.init() reads since #25099.
- StabilityDevice (button_stability_tests.be) lacks it too.
Tests only; no firmware files change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Shutter and Shutter + Tilt plugins handle UpOrOpen, DownOrClose,
StopMotion and GoToLiftPercentage (and GoToTiltPercentage for tilt) in
invoke_request(), but AcceptedCommandList (0xFFF9) of the Window
Covering cluster was empty. Controllers that build their feature set
from this list, such as Home Assistant, then don't offer setting the
position or tilt.
Override get_accepted_commands() in both plugins to report the
commands they implement.
Fixes #25146
* AsyncHttpClientLight: time out reading a body when the server goes silent
writeToStreamDataBlock() waited for the rest of a body for as long as
connected() held. A server that stops sending part way through without
closing leaves connected() true, so the worker never finishes and the
caller, waiting in runJob(), never returns. Give up after _tcpTimeout
without data and return HTTPC_ERROR_READ_TIMEOUT, as HttpClientLight
now does (#25139).
* AsyncHttpClientLight: notice when the server closes after the last byte
AsyncTcpAdapter only saw the peer close inside read(), and callers only
read when available() > 0. A body without Content-Length that ends with
the server closing left connected() true for ever, so
writeToStreamDataBlock() never returned. connected() now peeks at the
socket, as WiFiClient::connected() does: data still buffered keeps it
connected, an orderly close after it does not.
* AsyncHttpClientLight: address review of the read timeout
- connected() only reports a peer close and no longer calls stop(): it can
be called from another task than the worker using the socket.
- writeToStreamDataBlock() closes the connection on a read timeout, as the
chunked readers do, so a stalled connection is not reused.
- readBodyToStreamString(): the identity branch used by async GET/POST now
times out after _tcpTimeout without data, and notices a close: with no
Content-Length it ends the body, with one it is a lost connection.
After the zero-size chunk the readers read one line unconditionally,
then waited for another empty line. The first read already consumed the
empty line that ends the body, so every chunked body waited for a line
that never came and failed with HTTPC_ERROR_READ_TIMEOUT after
_tcpTimeout. Read trailer lines up to the first empty one instead.
writeToStreamDataBlock() looped for as long as connected() held, waiting
for the rest of the body with no time limit. A server that stops sending
part way through a body without closing the connection - a computer that
sleeps, a cable pulled, a network that drops between the two ends - leaves
connected() true for ever, so the read never returned. Called from the main
loop, as Berry's webclient get_string() is, it left the device answering
nothing, not even its web page, until it was power-cycled.
Give up after _tcpTimeout without data, returning HTTPC_ERROR_READ_TIMEOUT,
as handleHeaderResponse() already does for the headers. A body that keeps
arriving, however slowly, is unaffected: each piece resets the clock.