mirror of
https://github.com/esphome/aioesphomeapi.git
synced 2024-11-26 12:45:26 +01:00
Add Bluetooth GATT calls (#259)
This commit is contained in:
parent
0e1ae31667
commit
6a82766553
@ -27,7 +27,6 @@ service APIConnection {
|
|||||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||||
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||||
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||||
rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
|
||||||
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
||||||
option (needs_authentication) = false;
|
option (needs_authentication) = false;
|
||||||
}
|
}
|
||||||
@ -45,6 +44,15 @@ service APIConnection {
|
|||||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||||
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
||||||
|
|
||||||
|
rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||||
|
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||||
|
rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {}
|
||||||
|
rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {}
|
||||||
|
rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {}
|
||||||
|
rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {}
|
||||||
|
rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
|
||||||
|
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1138,7 +1146,7 @@ message SubscribeBluetoothLEAdvertisementsRequest {
|
|||||||
|
|
||||||
message BluetoothServiceData {
|
message BluetoothServiceData {
|
||||||
string uuid = 1;
|
string uuid = 1;
|
||||||
repeated uint32 data = 2 [packed=false];
|
repeated uint32 data = 2 [packed=true];
|
||||||
}
|
}
|
||||||
message BluetoothLEAdvertisementResponse {
|
message BluetoothLEAdvertisementResponse {
|
||||||
option (id) = 67;
|
option (id) = 67;
|
||||||
@ -1154,3 +1162,163 @@ message BluetoothLEAdvertisementResponse {
|
|||||||
repeated BluetoothServiceData service_data = 5;
|
repeated BluetoothServiceData service_data = 5;
|
||||||
repeated BluetoothServiceData manufacturer_data = 6;
|
repeated BluetoothServiceData manufacturer_data = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BluetoothDeviceRequestType {
|
||||||
|
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
|
||||||
|
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
||||||
|
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
||||||
|
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothDeviceRequest {
|
||||||
|
option (id) = 68;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
BluetoothDeviceRequestType request_type = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothDeviceConnectionResponse {
|
||||||
|
option (id) = 69;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
bool connected = 2;
|
||||||
|
uint32 mtu = 3;
|
||||||
|
int32 error = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTGetServicesRequest {
|
||||||
|
option (id) = 70;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTDescriptor {
|
||||||
|
string uuid = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
string description = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTCharacteristic {
|
||||||
|
string uuid = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
uint32 properties = 3;
|
||||||
|
repeated BluetoothGATTDescriptor descriptors = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTService {
|
||||||
|
string uuid = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
repeated BluetoothGATTCharacteristic characteristics = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTGetServicesResponse {
|
||||||
|
option (id) = 71;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
repeated BluetoothGATTService services = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTGetServicesDoneResponse {
|
||||||
|
option (id) = 72;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTReadRequest {
|
||||||
|
option (id) = 73;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTReadResponse {
|
||||||
|
option (id) = 74;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
|
||||||
|
bytes data = 3;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTWriteRequest {
|
||||||
|
option (id) = 75;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
bool response = 3;
|
||||||
|
|
||||||
|
bytes data = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTReadDescriptorRequest {
|
||||||
|
option (id) = 76;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTWriteDescriptorRequest {
|
||||||
|
option (id) = 77;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
|
||||||
|
bytes data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTNotifyRequest {
|
||||||
|
option (id) = 78;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
bool enable = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothGATTNotifyDataResponse {
|
||||||
|
option (id) = 79;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 handle = 2;
|
||||||
|
|
||||||
|
bytes data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubscribeBluetoothConnectionsFreeRequest {
|
||||||
|
option (id) = 80;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
}
|
||||||
|
|
||||||
|
message BluetoothConnectionsFreeResponse {
|
||||||
|
option (id) = 81;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
|
uint32 free = 1;
|
||||||
|
uint32 limit = 2;
|
||||||
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,8 +1,11 @@
|
|||||||
|
# pylint: disable=too-many-lines
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
Callable,
|
Callable,
|
||||||
|
Coroutine,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
@ -12,10 +15,24 @@ from typing import (
|
|||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
from google.protobuf import message
|
from google.protobuf import message
|
||||||
|
|
||||||
from .api_pb2 import ( # type: ignore
|
from .api_pb2 import ( # type: ignore
|
||||||
BinarySensorStateResponse,
|
BinarySensorStateResponse,
|
||||||
|
BluetoothConnectionsFreeResponse,
|
||||||
|
BluetoothDeviceConnectionResponse,
|
||||||
|
BluetoothDeviceRequest,
|
||||||
|
BluetoothGATTGetServicesDoneResponse,
|
||||||
|
BluetoothGATTGetServicesRequest,
|
||||||
|
BluetoothGATTGetServicesResponse,
|
||||||
|
BluetoothGATTNotifyDataResponse,
|
||||||
|
BluetoothGATTNotifyRequest,
|
||||||
|
BluetoothGATTReadDescriptorRequest,
|
||||||
|
BluetoothGATTReadRequest,
|
||||||
|
BluetoothGATTReadResponse,
|
||||||
|
BluetoothGATTWriteDescriptorRequest,
|
||||||
|
BluetoothGATTWriteRequest,
|
||||||
BluetoothLEAdvertisementResponse,
|
BluetoothLEAdvertisementResponse,
|
||||||
ButtonCommandRequest,
|
ButtonCommandRequest,
|
||||||
CameraImageRequest,
|
CameraImageRequest,
|
||||||
@ -63,6 +80,7 @@ from .api_pb2 import ( # type: ignore
|
|||||||
SensorStateResponse,
|
SensorStateResponse,
|
||||||
SirenCommandRequest,
|
SirenCommandRequest,
|
||||||
SirenStateResponse,
|
SirenStateResponse,
|
||||||
|
SubscribeBluetoothConnectionsFreeRequest,
|
||||||
SubscribeBluetoothLEAdvertisementsRequest,
|
SubscribeBluetoothLEAdvertisementsRequest,
|
||||||
SubscribeHomeassistantServicesRequest,
|
SubscribeHomeassistantServicesRequest,
|
||||||
SubscribeHomeAssistantStateResponse,
|
SubscribeHomeAssistantStateResponse,
|
||||||
@ -75,12 +93,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,
|
||||||
|
BluetoothConnectionsFree,
|
||||||
|
BluetoothDeviceConnection,
|
||||||
|
BluetoothDeviceRequestType,
|
||||||
|
BluetoothGATTRead,
|
||||||
|
BluetoothGATTServices,
|
||||||
BluetoothLEAdvertisement,
|
BluetoothLEAdvertisement,
|
||||||
ButtonInfo,
|
ButtonInfo,
|
||||||
CameraInfo,
|
CameraInfo,
|
||||||
@ -384,7 +407,7 @@ class APIClient:
|
|||||||
|
|
||||||
async def subscribe_bluetooth_le_advertisements(
|
async def subscribe_bluetooth_le_advertisements(
|
||||||
self, on_bluetooth_le_advertisement: Callable[[BluetoothLEAdvertisement], None]
|
self, on_bluetooth_le_advertisement: Callable[[BluetoothLEAdvertisement], None]
|
||||||
) -> None:
|
) -> Callable[[], None]:
|
||||||
self._check_authenticated()
|
self._check_authenticated()
|
||||||
|
|
||||||
def on_msg(msg: message.Message) -> None:
|
def on_msg(msg: message.Message) -> None:
|
||||||
@ -396,6 +419,227 @@ class APIClient:
|
|||||||
SubscribeBluetoothLEAdvertisementsRequest(), on_msg
|
SubscribeBluetoothLEAdvertisementsRequest(), on_msg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def unsub() -> None:
|
||||||
|
assert self._connection is not None
|
||||||
|
self._connection.remove_message_callback(on_msg)
|
||||||
|
|
||||||
|
return unsub
|
||||||
|
|
||||||
|
async def subscribe_bluetooth_connections_free(
|
||||||
|
self, on_bluetooth_connections_free_update: Callable[[int, int], None]
|
||||||
|
) -> Callable[[], None]:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
def on_msg(msg: message.Message) -> None:
|
||||||
|
if isinstance(msg, BluetoothConnectionsFreeResponse):
|
||||||
|
resp = BluetoothConnectionsFree.from_pb(msg)
|
||||||
|
on_bluetooth_connections_free_update(resp.free, resp.limit)
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message_callback_response(
|
||||||
|
SubscribeBluetoothConnectionsFreeRequest(), on_msg
|
||||||
|
)
|
||||||
|
|
||||||
|
def unsub() -> None:
|
||||||
|
assert self._connection is not None
|
||||||
|
self._connection.remove_message_callback(on_msg)
|
||||||
|
|
||||||
|
return unsub
|
||||||
|
|
||||||
|
async def bluetooth_device_connect(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
on_bluetooth_connection_state: Callable[[bool, int, int], None],
|
||||||
|
timeout: float = 10.0,
|
||||||
|
) -> Callable[[], None]:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
event = asyncio.Event()
|
||||||
|
|
||||||
|
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, resp.mtu, resp.error)
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
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 event.wait()
|
||||||
|
except asyncio.TimeoutError as err:
|
||||||
|
raise TimeoutAPIError("Timeout waiting for connect response") from err
|
||||||
|
|
||||||
|
def unsub() -> None:
|
||||||
|
assert self._connection is not None
|
||||||
|
self._connection.remove_message_callback(on_msg)
|
||||||
|
|
||||||
|
return unsub
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
def do_append(msg: message.Message) -> bool:
|
||||||
|
return isinstance(msg, BluetoothGATTGetServicesResponse)
|
||||||
|
|
||||||
|
def do_stop(msg: message.Message) -> bool:
|
||||||
|
return isinstance(msg, BluetoothGATTGetServicesDoneResponse)
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
resp = await self._connection.send_message_await_response_complex(
|
||||||
|
BluetoothGATTGetServicesRequest(address=address), do_append, do_stop
|
||||||
|
)
|
||||||
|
services = []
|
||||||
|
for msg in resp:
|
||||||
|
services.extend(BluetoothGATTServices.from_pb(msg).services)
|
||||||
|
return BluetoothGATTServices(address=address, services=services)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_read(
|
||||||
|
self, address: int, characteristic_handle: int, timeout: float = 10.0
|
||||||
|
) -> bytearray:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
req = BluetoothGATTReadRequest()
|
||||||
|
req.address = address
|
||||||
|
req.handle = characteristic_handle
|
||||||
|
|
||||||
|
def is_response(msg: message.Message) -> bool:
|
||||||
|
if isinstance(msg, BluetoothGATTReadResponse):
|
||||||
|
read = BluetoothGATTRead.from_pb(msg)
|
||||||
|
|
||||||
|
if read.address == address and read.handle == characteristic_handle:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
resp = await self._connection.send_message_await_response_complex(
|
||||||
|
req, is_response, is_response, timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(resp) != 1:
|
||||||
|
raise APIConnectionError(f"Expected one result, got {len(resp)}")
|
||||||
|
|
||||||
|
read_response = BluetoothGATTRead.from_pb(resp[0])
|
||||||
|
|
||||||
|
return bytearray(read_response.data)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_write(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
characteristic_handle: int,
|
||||||
|
data: bytes,
|
||||||
|
response: bool,
|
||||||
|
) -> None:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
req = BluetoothGATTWriteRequest()
|
||||||
|
req.address = address
|
||||||
|
req.handle = characteristic_handle
|
||||||
|
req.response = response
|
||||||
|
req.data = data
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message(req)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_read_descriptor(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
handle: int,
|
||||||
|
timeout: float = 10.0,
|
||||||
|
) -> bytearray:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
req = BluetoothGATTReadDescriptorRequest()
|
||||||
|
req.address = address
|
||||||
|
req.handle = handle
|
||||||
|
|
||||||
|
def is_response(msg: message.Message) -> bool:
|
||||||
|
if isinstance(msg, BluetoothGATTReadResponse):
|
||||||
|
read = BluetoothGATTRead.from_pb(msg)
|
||||||
|
|
||||||
|
if read.address == address and read.handle == handle:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
resp = await self._connection.send_message_await_response_complex(
|
||||||
|
req, is_response, is_response, timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(resp) != 1:
|
||||||
|
raise APIConnectionError(f"Expected one result, got {len(resp)}")
|
||||||
|
|
||||||
|
read_response = BluetoothGATTRead.from_pb(resp[0])
|
||||||
|
|
||||||
|
return bytearray(read_response.data)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_write_descriptor(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
handle: int,
|
||||||
|
data: bytes,
|
||||||
|
) -> None:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
req = BluetoothGATTWriteDescriptorRequest()
|
||||||
|
req.address = address
|
||||||
|
req.handle = handle
|
||||||
|
req.data = data
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message(req)
|
||||||
|
|
||||||
|
async def bluetooth_gatt_start_notify(
|
||||||
|
self,
|
||||||
|
address: int,
|
||||||
|
handle: int,
|
||||||
|
on_bluetooth_gatt_notify: Callable[[int, bytearray], None],
|
||||||
|
) -> Callable[[], Coroutine[Any, Any, None]]:
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
def on_msg(msg: message.Message) -> None:
|
||||||
|
if isinstance(msg, BluetoothGATTNotifyDataResponse):
|
||||||
|
notify = BluetoothGATTRead.from_pb(msg)
|
||||||
|
if address == notify.address and handle == notify.handle:
|
||||||
|
on_bluetooth_gatt_notify(handle, bytearray(notify.data))
|
||||||
|
|
||||||
|
assert self._connection is not None
|
||||||
|
await self._connection.send_message_callback_response(
|
||||||
|
BluetoothGATTNotifyRequest(address=address, handle=handle, enable=True),
|
||||||
|
on_msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def stop_notify() -> None:
|
||||||
|
assert self._connection is not None
|
||||||
|
self._connection.remove_message_callback(on_msg)
|
||||||
|
|
||||||
|
self._check_authenticated()
|
||||||
|
|
||||||
|
await self._connection.send_message(
|
||||||
|
BluetoothGATTNotifyRequest(address=address, handle=handle, enable=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
return stop_notify
|
||||||
|
|
||||||
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:
|
||||||
|
@ -362,6 +362,10 @@ class APIConnection:
|
|||||||
await self._report_fatal_error(err)
|
await self._report_fatal_error(err)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def remove_message_callback(self, on_message: Callable[[Any], None]) -> None:
|
||||||
|
"""Remove a message callback."""
|
||||||
|
self._message_handlers.remove(on_message)
|
||||||
|
|
||||||
async def send_message_callback_response(
|
async def send_message_callback_response(
|
||||||
self, send_msg: message.Message, on_message: Callable[[Any], None]
|
self, send_msg: message.Message, on_message: Callable[[Any], None]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
from .api_pb2 import ( # type: ignore
|
from .api_pb2 import ( # type: ignore
|
||||||
BinarySensorStateResponse,
|
BinarySensorStateResponse,
|
||||||
|
BluetoothConnectionsFreeResponse,
|
||||||
|
BluetoothDeviceConnectionResponse,
|
||||||
|
BluetoothDeviceRequest,
|
||||||
|
BluetoothGATTGetServicesDoneResponse,
|
||||||
|
BluetoothGATTGetServicesRequest,
|
||||||
|
BluetoothGATTGetServicesResponse,
|
||||||
|
BluetoothGATTNotifyDataResponse,
|
||||||
|
BluetoothGATTNotifyRequest,
|
||||||
|
BluetoothGATTReadDescriptorRequest,
|
||||||
|
BluetoothGATTReadRequest,
|
||||||
|
BluetoothGATTReadResponse,
|
||||||
|
BluetoothGATTWriteDescriptorRequest,
|
||||||
|
BluetoothGATTWriteRequest,
|
||||||
BluetoothLEAdvertisementResponse,
|
BluetoothLEAdvertisementResponse,
|
||||||
ButtonCommandRequest,
|
ButtonCommandRequest,
|
||||||
CameraImageRequest,
|
CameraImageRequest,
|
||||||
@ -56,6 +69,7 @@ from .api_pb2 import ( # type: ignore
|
|||||||
SensorStateResponse,
|
SensorStateResponse,
|
||||||
SirenCommandRequest,
|
SirenCommandRequest,
|
||||||
SirenStateResponse,
|
SirenStateResponse,
|
||||||
|
SubscribeBluetoothConnectionsFreeRequest,
|
||||||
SubscribeBluetoothLEAdvertisementsRequest,
|
SubscribeBluetoothLEAdvertisementsRequest,
|
||||||
SubscribeHomeassistantServicesRequest,
|
SubscribeHomeassistantServicesRequest,
|
||||||
SubscribeHomeAssistantStateResponse,
|
SubscribeHomeAssistantStateResponse,
|
||||||
@ -193,4 +207,18 @@ MESSAGE_TYPE_TO_PROTO = {
|
|||||||
65: MediaPlayerCommandRequest,
|
65: MediaPlayerCommandRequest,
|
||||||
66: SubscribeBluetoothLEAdvertisementsRequest,
|
66: SubscribeBluetoothLEAdvertisementsRequest,
|
||||||
67: BluetoothLEAdvertisementResponse,
|
67: BluetoothLEAdvertisementResponse,
|
||||||
|
68: BluetoothDeviceRequest,
|
||||||
|
69: BluetoothDeviceConnectionResponse,
|
||||||
|
70: BluetoothGATTGetServicesRequest,
|
||||||
|
71: BluetoothGATTGetServicesResponse,
|
||||||
|
72: BluetoothGATTGetServicesDoneResponse,
|
||||||
|
73: BluetoothGATTReadRequest,
|
||||||
|
74: BluetoothGATTReadResponse,
|
||||||
|
75: BluetoothGATTWriteRequest,
|
||||||
|
76: BluetoothGATTReadDescriptorRequest,
|
||||||
|
77: BluetoothGATTWriteDescriptorRequest,
|
||||||
|
78: BluetoothGATTNotifyRequest,
|
||||||
|
79: BluetoothGATTNotifyDataResponse,
|
||||||
|
80: SubscribeBluetoothConnectionsFreeRequest,
|
||||||
|
81: BluetoothConnectionsFreeResponse,
|
||||||
}
|
}
|
||||||
|
@ -759,7 +759,7 @@ def _long_uuid(uuid: str) -> str:
|
|||||||
"""Convert a UUID to a long UUID."""
|
"""Convert a UUID to a long UUID."""
|
||||||
return (
|
return (
|
||||||
f"0000{uuid[2:].lower()}-0000-1000-8000-00805f9b34fb" if len(uuid) < 8 else uuid
|
f"0000{uuid[2:].lower()}-0000-1000-8000-00805f9b34fb" if len(uuid) < 8 else uuid
|
||||||
)
|
).lower()
|
||||||
|
|
||||||
|
|
||||||
def _convert_bluetooth_le_service_uuids(value: List[str]) -> List[str]:
|
def _convert_bluetooth_le_service_uuids(value: List[str]) -> List[str]:
|
||||||
@ -771,6 +771,7 @@ def _convert_bluetooth_le_service_data(
|
|||||||
) -> Dict[str, bytes]:
|
) -> Dict[str, bytes]:
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return {_long_uuid(v.uuid): bytes(v.data) for v in value} # type: ignore
|
return {_long_uuid(v.uuid): bytes(v.data) for v in value} # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@ -799,6 +800,100 @@ class BluetoothLEAdvertisement(APIModelBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothDeviceConnection(APIModelBase):
|
||||||
|
address: int = 0
|
||||||
|
connected: bool = False
|
||||||
|
mtu: int = 0
|
||||||
|
error: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTRead(APIModelBase):
|
||||||
|
address: int = 0
|
||||||
|
handle: int = 0
|
||||||
|
|
||||||
|
data: bytes = b""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothGATTDescriptor(APIModelBase):
|
||||||
|
uuid: str = converter_field(default="", converter=_long_uuid)
|
||||||
|
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 = converter_field(default="", converter=_long_uuid)
|
||||||
|
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 = converter_field(default="", converter=_long_uuid)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BluetoothConnectionsFree(APIModelBase):
|
||||||
|
free: int = 0
|
||||||
|
limit: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
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