Implement MQTT for Advanced LVA Controls
Includes various compatibility fixes, reversions, and clean-up from the previous development cycle to create a stable base for the MQTT implementation. This ensures the application starts reliably with the pinned aioesphomeapi version.
This commit is contained in:
@@ -9,8 +9,6 @@ from typing import TYPE_CHECKING, List, Optional
|
|||||||
# pylint: disable=no-name-in-module
|
# pylint: disable=no-name-in-module
|
||||||
from aioesphomeapi._frame_helper.packets import make_plain_text_packets
|
from aioesphomeapi._frame_helper.packets import make_plain_text_packets
|
||||||
from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
|
from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
|
||||||
ConnectRequest,
|
|
||||||
ConnectResponse,
|
|
||||||
DisconnectRequest,
|
DisconnectRequest,
|
||||||
DisconnectResponse,
|
DisconnectResponse,
|
||||||
HelloRequest,
|
HelloRequest,
|
||||||
@@ -57,9 +55,7 @@ class APIServer(asyncio.Protocol):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(msg_inst, ConnectRequest):
|
if isinstance(msg_inst, DisconnectRequest):
|
||||||
self.send_messages([ConnectResponse()])
|
|
||||||
elif isinstance(msg_inst, DisconnectRequest):
|
|
||||||
self.send_messages([DisconnectResponse()])
|
self.send_messages([DisconnectResponse()])
|
||||||
_LOGGER.debug("Disconnect requested")
|
_LOGGER.debug("Disconnect requested")
|
||||||
if self._transport:
|
if self._transport:
|
||||||
@@ -99,7 +95,6 @@ class APIServer(asyncio.Protocol):
|
|||||||
|
|
||||||
while self._buffer_len >= 3:
|
while self._buffer_len >= 3:
|
||||||
self._pos = 0
|
self._pos = 0
|
||||||
# Read preamble, which should always 0x00
|
|
||||||
if (preamble := self._read_varuint()) != 0x00:
|
if (preamble := self._read_varuint()) != 0x00:
|
||||||
_LOGGER.error("Incorrect preamble: %s", preamble)
|
_LOGGER.error("Incorrect preamble: %s", preamble)
|
||||||
return
|
return
|
||||||
@@ -113,7 +108,6 @@ class APIServer(asyncio.Protocol):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if length == 0:
|
if length == 0:
|
||||||
# Empty message (allowed)
|
|
||||||
self._remove_from_buffer()
|
self._remove_from_buffer()
|
||||||
self.process_packet(msg_type, b"")
|
self.process_packet(msg_type, b"")
|
||||||
continue
|
continue
|
||||||
@@ -125,7 +119,6 @@ class APIServer(asyncio.Protocol):
|
|||||||
self.process_packet(msg_type, packet_data)
|
self.process_packet(msg_type, packet_data)
|
||||||
|
|
||||||
def _read(self, length: int) -> bytes | None:
|
def _read(self, length: int) -> bytes | None:
|
||||||
"""Read exactly length bytes from the buffer or None if all the bytes are not yet available."""
|
|
||||||
new_pos = self._pos + length
|
new_pos = self._pos + length
|
||||||
if self._buffer_len < new_pos:
|
if self._buffer_len < new_pos:
|
||||||
return None
|
return None
|
||||||
@@ -134,8 +127,6 @@ class APIServer(asyncio.Protocol):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
assert self._buffer is not None, "Buffer should be set"
|
assert self._buffer is not None, "Buffer should be set"
|
||||||
cstr = self._buffer
|
cstr = self._buffer
|
||||||
# Important: we must keep the bounds check (self._buffer_len < new_pos)
|
|
||||||
# above to verify we never try to read past the end of the buffer
|
|
||||||
return cstr[original_pos:new_pos]
|
return cstr[original_pos:new_pos]
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
@@ -143,7 +134,6 @@ class APIServer(asyncio.Protocol):
|
|||||||
self._writelines = None
|
self._writelines = None
|
||||||
|
|
||||||
def _read_varuint(self) -> int:
|
def _read_varuint(self) -> int:
|
||||||
"""Read a varuint from the buffer or -1 if the buffer runs out of bytes."""
|
|
||||||
if not self._buffer:
|
if not self._buffer:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
@@ -160,21 +150,12 @@ class APIServer(asyncio.Protocol):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
def _remove_from_buffer(self) -> None:
|
def _remove_from_buffer(self) -> None:
|
||||||
"""Remove data from the buffer."""
|
|
||||||
end_of_frame_pos = self._pos
|
end_of_frame_pos = self._pos
|
||||||
self._buffer_len -= end_of_frame_pos
|
self._buffer_len -= end_of_frame_pos
|
||||||
if self._buffer_len == 0:
|
if self._buffer_len == 0:
|
||||||
# This is the best case scenario, we can just set the buffer to None
|
|
||||||
# and don't have to copy the data. This is the most common case as well.
|
|
||||||
self._buffer = None
|
self._buffer = None
|
||||||
return
|
return
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
assert self._buffer is not None, "Buffer should be set"
|
assert self._buffer is not None, "Buffer should be set"
|
||||||
# This is the worst case scenario, we have to copy the data
|
|
||||||
# and can't just use the buffer directly. This should only happen
|
|
||||||
# when we read multiple frames at once because the event loop
|
|
||||||
# is blocked and we cannot pull the data out of the buffer fast enough.
|
|
||||||
cstr = self._buffer
|
cstr = self._buffer
|
||||||
# Important: we must use the explicit length for the slice
|
|
||||||
# since Cython will stop at any '\0' character if we don't
|
|
||||||
self._buffer = cstr[end_of_frame_pos : self._buffer_len + end_of_frame_pos]
|
self._buffer = cstr[end_of_frame_pos : self._buffer_len + end_of_frame_pos]
|
||||||
|
|||||||
Reference in New Issue
Block a user