mirror of
https://github.com/esphome/aioesphomeapi.git
synced 2024-11-28 13:05:12 +01:00
Add GATT functions to client
This commit is contained in:
parent
c65011094e
commit
a671b723d0
@ -1,3 +1,5 @@
|
|||||||
|
import asyncio
|
||||||
|
import async_timeout
|
||||||
import logging
|
import logging
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
@ -16,6 +18,14 @@ from google.protobuf import message
|
|||||||
|
|
||||||
from .api_pb2 import ( # type: ignore
|
from .api_pb2 import ( # type: ignore
|
||||||
BinarySensorStateResponse,
|
BinarySensorStateResponse,
|
||||||
|
BluetoothDeviceConnectionResponse,
|
||||||
|
BluetoothDeviceRequest,
|
||||||
|
BluetoothGATTGetServicesRequest,
|
||||||
|
BluetoothGATTGetServicesResponse,
|
||||||
|
BluetoothGATTReadRequest,
|
||||||
|
BluetoothGATTReadResponse,
|
||||||
|
BluetoothGATTWriteRequest,
|
||||||
|
BluetoothGATTWriteResponse,
|
||||||
BluetoothLEAdvertisementResponse,
|
BluetoothLEAdvertisementResponse,
|
||||||
ButtonCommandRequest,
|
ButtonCommandRequest,
|
||||||
CameraImageRequest,
|
CameraImageRequest,
|
||||||
@ -75,12 +85,17 @@ from .api_pb2 import ( # type: ignore
|
|||||||
TextSensorStateResponse,
|
TextSensorStateResponse,
|
||||||
)
|
)
|
||||||
from .connection import APIConnection, ConnectionParams
|
from .connection import APIConnection, ConnectionParams
|
||||||
from .core import APIConnectionError
|
from .core import APIConnectionError, TimeoutAPIError
|
||||||
from .host_resolver import ZeroconfInstanceType
|
from .host_resolver import ZeroconfInstanceType
|
||||||
from .model import (
|
from .model import (
|
||||||
APIVersion,
|
APIVersion,
|
||||||
BinarySensorInfo,
|
BinarySensorInfo,
|
||||||
BinarySensorState,
|
BinarySensorState,
|
||||||
|
BluetoothDeviceConnection,
|
||||||
|
BluetoothDeviceRequestType,
|
||||||
|
BluetoothGATTRead,
|
||||||
|
BluetoothGATTServices,
|
||||||
|
BluetoothGATTWrite,
|
||||||
BluetoothLEAdvertisement,
|
BluetoothLEAdvertisement,
|
||||||
ButtonInfo,
|
ButtonInfo,
|
||||||
CameraInfo,
|
CameraInfo,
|
||||||
@ -396,6 +411,98 @@ class APIClient:
|
|||||||
SubscribeBluetoothLEAdvertisementsRequest(), on_msg
|
SubscribeBluetoothLEAdvertisementsRequest(), on_msg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def bluetooth_device_connect(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
on_bluetooth_connection_state: Callable[[bool], None],
|
||||||
|
timeout: float = 10.0,
|
||||||
|
) -> None:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
fut = asyncio.get_event_loop().create_future()
|
||||||
|
|
||||||
|
def on_msg(msg: message.Message) -> None:
|
||||||
|
if isinstance(msg, BluetoothDeviceConnectionResponse):
|
||||||
|
resp = BluetoothDeviceConnection.from_pb(msg)
|
||||||
|
if address == resp.address:
|
||||||
|
on_bluetooth_connection_state(resp.connected)
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message_callback_response(
|
||||||
|
BluetoothDeviceRequest(
|
||||||
|
address=address,
|
||||||
|
request_type=BluetoothDeviceRequestType.CONNECT,
|
||||||
|
),
|
||||||
|
on_msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(timeout):
|
||||||
|
await fut
|
||||||
|
except asyncio.TimeoutError as err:
|
||||||
|
raise TimeoutAPIError(f"Timeout waiting for connect response") from err
|
||||||
|
|
||||||
|
async def bluetooth_device_disconnect(self, address: int) -> None:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message(
|
||||||
|
BluetoothDeviceRequest(
|
||||||
|
address=address,
|
||||||
|
request_type=BluetoothDeviceRequestType.DISCONNECT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_get_services(self, address: int) -> BluetoothGATTServices:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
resp = await self._connection.send_message_await_response(
|
||||||
|
BluetoothGATTGetServicesRequest(address=address),
|
||||||
|
BluetoothGATTGetServicesResponse,
|
||||||
|
)
|
||||||
|
return BluetoothGATTServices.from_pb(resp)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_read(
|
||||||
|
self, address: int, service: str, characteristic: str
|
||||||
|
) -> bytearray:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
req = BluetoothGATTReadRequest()
|
||||||
|
req.address = address
|
||||||
|
req.service = service
|
||||||
|
req.characteristic = characteristic
|
||||||
|
req.is_descriptor = False
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
resp = await self._connection.send_message_await_response(
|
||||||
|
req, BluetoothGATTReadResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
read_response = BluetoothGATTRead.from_pb(resp)
|
||||||
|
if (
|
||||||
|
read_response.address == address
|
||||||
|
and (service == "" or read_response.service == service)
|
||||||
|
and read_response.characteristic == characteristic
|
||||||
|
):
|
||||||
|
return bytearray(read_response.data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def bluetooth_gatt_write(
|
||||||
|
self, address: int, service: str, characteristic: str, data: bytes
|
||||||
|
) -> None:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
req = BluetoothGATTWriteRequest()
|
||||||
|
req.address = address
|
||||||
|
req.service = service
|
||||||
|
req.characteristic = characteristic
|
||||||
|
req.is_descriptor = False
|
||||||
|
req.data = data
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message(req)
|
||||||
|
|
||||||
async def subscribe_home_assistant_states(
|
async def subscribe_home_assistant_states(
|
||||||
self, on_state_sub: Callable[[str, Optional[str]], None]
|
self, on_state_sub: Callable[[str, Optional[str]], None]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -799,6 +799,99 @@ class BluetoothLEAdvertisement(APIModelBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothDeviceConnection(APIModelBase):
|
||||||
|
address: int = 0
|
||||||
|
connected: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTRead(APIModelBase):
|
||||||
|
address: int = 0
|
||||||
|
is_descriptor: bool = False
|
||||||
|
service_uuid: str = ""
|
||||||
|
characteristic_uuid: str = ""
|
||||||
|
|
||||||
|
data: bytes = converter_field(default_factory=bytes, converter=bytes)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTWrite(APIModelBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTDescriptor(APIModelBase):
|
||||||
|
uuid: str = ""
|
||||||
|
handle: int = 0
|
||||||
|
description: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_list(cls, value: List[Any]) -> List["BluetoothGATTDescriptor"]:
|
||||||
|
ret = []
|
||||||
|
for x in value:
|
||||||
|
if isinstance(x, dict):
|
||||||
|
ret.append(cls.from_dict(x))
|
||||||
|
else:
|
||||||
|
ret.append(cls.from_pb(x))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTCharacteristic(APIModelBase):
|
||||||
|
uuid: str = ""
|
||||||
|
handle: int = 0
|
||||||
|
properties: int = 0
|
||||||
|
|
||||||
|
descriptors: List[BluetoothGATTDescriptor] = converter_field(
|
||||||
|
default_factory=list, converter=BluetoothGATTDescriptor.convert_list
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_list(cls, value: List[Any]) -> List["BluetoothGATTCharacteristic"]:
|
||||||
|
ret = []
|
||||||
|
for x in value:
|
||||||
|
if isinstance(x, dict):
|
||||||
|
ret.append(cls.from_dict(x))
|
||||||
|
else:
|
||||||
|
ret.append(cls.from_pb(x))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTService(APIModelBase):
|
||||||
|
uuid: str = ""
|
||||||
|
handle: int = 0
|
||||||
|
characteristics: List[BluetoothGATTCharacteristic] = converter_field(
|
||||||
|
default_factory=list, converter=BluetoothGATTCharacteristic.convert_list
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_list(cls, value: List[Any]) -> List["BluetoothGATTService"]:
|
||||||
|
ret = []
|
||||||
|
for x in value:
|
||||||
|
if isinstance(x, dict):
|
||||||
|
ret.append(cls.from_dict(x))
|
||||||
|
else:
|
||||||
|
ret.append(cls.from_pb(x))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTServices(APIModelBase):
|
||||||
|
address: int = 0
|
||||||
|
services: List[BluetoothGATTService] = converter_field(
|
||||||
|
default_factory=list, converter=BluetoothGATTService.convert_list
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BluetoothDeviceRequestType(APIIntEnum):
|
||||||
|
CONNECT = 0
|
||||||
|
DISCONNECT = 1
|
||||||
|
PAIR = 2
|
||||||
|
UNPAIR = 3
|
||||||
|
|
||||||
|
|
||||||
class LogLevel(APIIntEnum):
|
class LogLevel(APIIntEnum):
|
||||||
LOG_LEVEL_NONE = 0
|
LOG_LEVEL_NONE = 0
|
||||||
LOG_LEVEL_ERROR = 1
|
LOG_LEVEL_ERROR = 1
|
||||||
|
Loading…
Reference in New Issue
Block a user