Change reads and writes to use handles

Add notify functions
This commit is contained in:
Jesse Hills 2022-09-28 09:51:52 +13:00
parent 6ae112f8f5
commit 0b7ca79234
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
4 changed files with 432 additions and 170 deletions

View File

@ -1178,6 +1178,7 @@ message BluetoothDeviceConnectionResponse {
uint64 address = 1;
bool connected = 2;
uint32 mtu = 3;
}
message BluetoothGATTGetServicesRequest {
@ -1223,10 +1224,7 @@ message BluetoothGATTReadRequest {
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
bool is_descriptor = 2;
string service_uuid = 3;
string characteristic_uuid = 4;
uint32 handle = 2;
}
message BluetoothGATTReadResponse {
@ -1235,11 +1233,9 @@ message BluetoothGATTReadResponse {
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
bool is_descriptor = 2;
string service_uuid = 3;
string characteristic_uuid = 4;
uint32 handle = 2;
repeated uint32 data = 5 [packed=true];
repeated uint32 data = 3 [packed=true];
}
@ -1249,17 +1245,49 @@ message BluetoothGATTWriteRequest {
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
bool is_descriptor = 2;
uint32 handle = 2;
bool response = 3;
string service_uuid = 3;
string characteristic_uuid = 4;
repeated uint32 data = 5 [packed=true];
repeated uint32 data = 4 [packed=true];
}
message BluetoothGATTWriteResponse {
message BluetoothGATTReadDescriptorRequest {
option (id) = 77;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}
message BluetoothGATTWriteDescriptorRequest {
option (id) = 78;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
repeated uint32 data = 3 [packed=true];
}
message BluetoothGATTNotifyRequest {
option (id) = 79;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
bool enable = 3;
}
message BluetoothGATTNotifyDataResponse {
option (id) = 80;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
repeated uint32 data = 3 [packed=true];
}

File diff suppressed because one or more lines are too long

View File

@ -20,8 +20,12 @@ from .api_pb2 import ( # type: ignore
BinarySensorStateResponse,
BluetoothDeviceConnectionResponse,
BluetoothDeviceRequest,
BluetoothGATTReadDescriptorRequest,
BluetoothGATTWriteDescriptorRequest,
BluetoothGATTGetServicesRequest,
BluetoothGATTGetServicesResponse,
BluetoothGATTNotifyRequest,
BluetoothGATTNotifyDataResponse,
BluetoothGATTReadRequest,
BluetoothGATTReadResponse,
BluetoothGATTWriteRequest,
@ -412,7 +416,7 @@ class APIClient:
async def bluetooth_device_connect(
self,
address: int,
on_bluetooth_connection_state: Callable[[bool], None],
on_bluetooth_connection_state: Callable[[bool, int], None],
timeout: float = 10.0,
) -> None:
self._check_authenticated()
@ -423,7 +427,7 @@ class APIClient:
if isinstance(msg, BluetoothDeviceConnectionResponse):
resp = BluetoothDeviceConnection.from_pb(msg)
if address == resp.address:
on_bluetooth_connection_state(resp.connected)
on_bluetooth_connection_state(resp.connected, resp.mtu)
assert self._connection is not None
await self._connection.send_message_callback_response(
@ -462,45 +466,128 @@ class APIClient:
return BluetoothGATTServices.from_pb(resp)
async def bluetooth_gatt_read(
self, address: int, service: str, characteristic: str
self, address: int, characteristic_handle: int, timeout: float = 10.0
) -> bytearray:
self._check_authenticated()
req = BluetoothGATTReadRequest()
req.address = address
req.service = service
req.characteristic = characteristic
req.is_descriptor = False
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(
req, BluetoothGATTReadResponse
resp = await self._connection.send_message_await_response_complex(
req, is_response, is_response, timeout=timeout
)
read_response = BluetoothGATTRead.from_pb(resp)
if (
read_response.address == address
and (service == "" or read_response.service_uuid == service)
and read_response.characteristic_uuid == characteristic
):
return bytearray(read_response.data)
return bytearray()
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, service: str, characteristic: str, data: bytes
self,
address: int,
characteristic_handle: int,
data: bytes,
response: bool,
) -> None:
self._check_authenticated()
req = BluetoothGATTWriteRequest()
req.address = address
req.service = service
req.characteristic = characteristic
req.is_descriptor = False
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],
) -> 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 bluetooth_gatt_stop_notify(self, address: int, handle: int) -> None:
self._check_authenticated()
assert self._connection is not None
await self._connection.send_message(
BluetoothGATTNotifyRequest(address=address, handle=handle, enable=False)
)
async def subscribe_home_assistant_states(
self, on_state_sub: Callable[[str, Optional[str]], None]
) -> None:

View File

@ -803,23 +803,17 @@ class BluetoothLEAdvertisement(APIModelBase):
class BluetoothDeviceConnection(APIModelBase):
address: int = 0
connected: bool = False
mtu: int = 0
@dataclass(frozen=True)
class BluetoothGATTRead(APIModelBase):
address: int = 0
is_descriptor: bool = False
service_uuid: str = ""
characteristic_uuid: str = ""
handle: int = 0
data: bytes = converter_field(default_factory=bytes, converter=bytes)
@dataclass(frozen=True)
class BluetoothGATTWrite(APIModelBase):
pass
@dataclass(frozen=True)
class BluetoothGATTDescriptor(APIModelBase):
uuid: str = ""