Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion aioesphomeapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
)
from .ble_defs import ESP_CONNECTION_ERROR_DESCRIPTION, BLEConnectionError
from .client import APIClient
from .connection import APIConnection, ConnectionParams
from .connection import (
APIConnection,
BLEAPIConnection,
BLEConnectionParams,
ConnectionParams,
IPAPIConnection,
IPConnectionParams,
)
from .core import (
ESPHOME_GATT_ERRORS,
MESSAGE_TYPE_TO_PROTO,
Expand Down
32 changes: 23 additions & 9 deletions aioesphomeapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@
on_subscribe_home_assistant_state_response,
on_zwave_proxy_request_message,
)
from .connection import APIConnection, ConnectionParams, handle_timeout # noqa: F401
from .connection import (
BLEAPIConnection,
IPAPIConnection,
IPConnectionParams,
handle_timeout,
)
from .core import (
APIConnectionError,
BluetoothConnectionDroppedError,
Expand Down Expand Up @@ -334,14 +339,23 @@ async def start_resolve_host(
if self._connection is not None:
msg = f"Already connected to {self.log_name}!"
raise APIConnectionError(msg)
self._connection = APIConnection(
self._params,
partial(self._on_stop, on_stop),
self._debug_enabled,
self.log_name,
log_errors=log_errors,
)
await self._execute_connection_coro(self._connection.start_resolve_host())
if isinstance(self._params, IPConnectionParams):
self._connection = IPAPIConnection(
self._params,
partial(self._on_stop, on_stop),
self._debug_enabled,
self.log_name,
log_errors=log_errors,
)
await self._execute_connection_coro(self._connection.start_resolve_host())
else:
self._connection = BLEAPIConnection(
self._params,
partial(self._on_stop, on_stop),
self._debug_enabled,
self.log_name,
log_errors=log_errors,
)

async def start_connection(self) -> None:
"""Start connecting to the device."""
Expand Down
70 changes: 51 additions & 19 deletions aioesphomeapi/client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import asyncio
import itertools
import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal

from ._frame_helper.base import ( # noqa: F401
MAX_NAME_LEN,
Expand All @@ -31,7 +31,7 @@
SubscribeHomeAssistantStateResponse,
ZWaveProxyRequest,
)
from .connection import ConnectionParams
from .connection import BLEConnectionParams, IPConnectionParams
from .core import APIConnectionError
from .model import (
APIVersion,
Expand Down Expand Up @@ -289,9 +289,11 @@ class APIClientBase:
def __init__(
self,
address: str_, # allow subclass str
port: int,
port: int | None = None,
password: str_ | None = None,
*,
transport: Literal["ip", "ble"] = "ip",
ble_address_type: Literal["public", "random"] | None = None,
client_info: str_ = "aioesphomeapi",
keepalive: float = KEEP_ALIVE_FREQUENCY,
zeroconf_instance: ZeroconfInstanceType | None = None,
Expand Down Expand Up @@ -330,22 +332,44 @@ def __init__(
request for the current time and timezone.
"""
self._debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
self._params = ConnectionParams(
addresses=[str(addr) for addr in addresses]
if addresses
else [str(address)],
port=port,
password=password,
client_info=client_info,
keepalive=keepalive,
zeroconf_manager=ZeroconfManager(zeroconf_instance),
# treat empty '' psk string as missing (like password)
noise_psk=_stringify_or_none(noise_psk) or None,
expected_name=_stringify_or_none(expected_name) or None,
expected_mac=_stringify_or_none(expected_mac) or None,
timezone=_stringify_or_none(timezone) or None,
provide_time=provide_time,
)
self._params: IPConnectionParams | BLEConnectionParams
if transport == "ip":
if port is None:
msg = "port must be provided for IP connections"
raise ValueError(msg)
self._params = IPConnectionParams(
addresses=[str(addr) for addr in addresses]
if addresses
else [str(address)],
port=port,
password=password,
client_info=client_info,
keepalive=keepalive,
zeroconf_manager=ZeroconfManager(zeroconf_instance),
# treat empty '' psk string as missing (like password)
noise_psk=_stringify_or_none(noise_psk) or None,
expected_name=_stringify_or_none(expected_name) or None,
expected_mac=_stringify_or_none(expected_mac) or None,
timezone=_stringify_or_none(timezone) or None,
provide_time=provide_time,
)
elif transport == "ble":
address = address.replace(":", "").lower()
if ble_address_type is None:
msg = "ble_address_type must be provided for BLE connections"
raise ValueError(msg)
self._params = BLEConnectionParams(
addresses=[address],
address_type=ble_address_type,
password=password,
client_info=client_info,
keepalive=keepalive,
# treat empty '' psk string as missing (like password)
noise_psk=_stringify_or_none(noise_psk) or None,
expected_name=_stringify_or_none(expected_name) or None,
timezone=_stringify_or_none(timezone) or None,
provide_time=provide_time,
)
self._connection: APIConnection | None = None
self._cached_device_info: DeviceInfo | None = None
self.cached_name: str | None = None
Expand All @@ -363,6 +387,11 @@ def set_debug(self, enabled: bool) -> None:

@property
def zeroconf_manager(self) -> ZeroconfManager:
if not isinstance(self._params, IPConnectionParams):
msg = (
"Zeroconf manager is only available for connections using IP transport"
)
raise TypeError(msg)
return self._params.zeroconf_manager

@property
Expand All @@ -383,6 +412,9 @@ def address(self) -> str:

@property
def port(self) -> int:
if not isinstance(self._params, IPConnectionParams):
msg = "Port is only available for connections using IP transport"
raise TypeError(msg)
return self._params.port

@property
Expand Down
35 changes: 25 additions & 10 deletions aioesphomeapi/connection.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ from ._frame_helper.base cimport APIFrameHelper
cdef dict MESSAGE_TYPE_TO_PROTO
cdef dict PROTO_TO_MESSAGE_TYPE

cdef set OPEN_STATES

cdef float KEEP_ALIVE_TIMEOUT_RATIO
cdef object HANDSHAKE_TIMEOUT

Expand Down Expand Up @@ -39,7 +37,7 @@ cdef object partial

cdef object hr

cdef object CONNECT_AND_SETUP_TIMEOUT, CONNECT_REQUEST_TIMEOUT
cdef object CONNECT_REQUEST_TIMEOUT

cdef object APIConnectionError
cdef object BadNameAPIError
Expand Down Expand Up @@ -82,18 +80,29 @@ cdef Py_ssize_t _MESSAGE_NUMBER_TO_PROTO_LEN
cdef class ConnectionParams:

cdef public list addresses
cdef public object port
cdef public object password
cdef public object client_info
cdef public object keepalive
cdef public object zeroconf_manager
cdef public object noise_psk
cdef public object expected_name
cdef public object expected_mac
cdef public object timezone
cdef public bint provide_time


@cython.dataclasses.dataclass
cdef class IPConnectionParams(ConnectionParams):
cdef public int port
cdef public object zeroconf_manager
cdef public object expected_mac


@cython.dataclasses.dataclass
cdef class BLEConnectionParams(ConnectionParams):
cdef public object address_type

cpdef void __post_init__(self)


cdef class APIConnection:

cdef ConnectionParams _params
Expand Down Expand Up @@ -142,8 +151,6 @@ cdef class APIConnection:

cdef void _async_schedule_keep_alive(self, object now) except *

cdef void _cleanup(self) except *

cpdef set_log_name(self, str name)

cdef _make_auth_request(self)
Expand Down Expand Up @@ -180,8 +187,16 @@ cdef class APIConnection:

cdef void _register_internal_message_handlers(self) except *

cdef void _increase_recv_buffer_size(self) except *

cdef void _set_start_connect_future(self) except *

cdef void _set_finish_connect_future(self) except *


cdef class IPAPIConnection(APIConnection):

cpdef void _set_resolve_host_future(self) except *
cdef void _increase_recv_buffer_size(self) except *


cdef class BLEAPIConnection(APIConnection):
pass
Loading
Loading