aioesphomeapi/aioesphomeapi/core.py
2024-06-11 16:29:13 +12:00

396 lines
11 KiB
Python

from __future__ import annotations
import re
from aioesphomeapi.model import BluetoothGATTError
from .api_pb2 import ( # type: ignore
AlarmControlPanelCommandRequest,
AlarmControlPanelStateResponse,
BinarySensorStateResponse,
BluetoothConnectionsFreeResponse,
BluetoothDeviceClearCacheResponse,
BluetoothDeviceConnectionResponse,
BluetoothDevicePairingResponse,
BluetoothDeviceRequest,
BluetoothDeviceUnpairingResponse,
BluetoothGATTErrorResponse,
BluetoothGATTGetServicesDoneResponse,
BluetoothGATTGetServicesRequest,
BluetoothGATTGetServicesResponse,
BluetoothGATTNotifyDataResponse,
BluetoothGATTNotifyRequest,
BluetoothGATTNotifyResponse,
BluetoothGATTReadDescriptorRequest,
BluetoothGATTReadRequest,
BluetoothGATTReadResponse,
BluetoothGATTWriteDescriptorRequest,
BluetoothGATTWriteRequest,
BluetoothGATTWriteResponse,
BluetoothLEAdvertisementResponse,
BluetoothLERawAdvertisementsResponse,
ButtonCommandRequest,
CameraImageRequest,
CameraImageResponse,
ClimateCommandRequest,
ClimateStateResponse,
ConnectRequest,
ConnectResponse,
CoverCommandRequest,
CoverStateResponse,
DateCommandRequest,
DateStateResponse,
DateTimeCommandRequest,
DateTimeStateResponse,
DeviceInfoRequest,
DeviceInfoResponse,
DisconnectRequest,
DisconnectResponse,
EventResponse,
ExecuteServiceRequest,
FanCommandRequest,
FanStateResponse,
GetTimeRequest,
GetTimeResponse,
HelloRequest,
HelloResponse,
HomeassistantServiceResponse,
HomeAssistantStateResponse,
LightCommandRequest,
LightStateResponse,
ListEntitiesAlarmControlPanelResponse,
ListEntitiesBinarySensorResponse,
ListEntitiesButtonResponse,
ListEntitiesCameraResponse,
ListEntitiesClimateResponse,
ListEntitiesCoverResponse,
ListEntitiesDateResponse,
ListEntitiesDateTimeResponse,
ListEntitiesDoneResponse,
ListEntitiesEventResponse,
ListEntitiesFanResponse,
ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesMediaPlayerResponse,
ListEntitiesNumberResponse,
ListEntitiesRequest,
ListEntitiesSelectResponse,
ListEntitiesSensorResponse,
ListEntitiesServicesResponse,
ListEntitiesSirenResponse,
ListEntitiesSwitchResponse,
ListEntitiesTextResponse,
ListEntitiesTextSensorResponse,
ListEntitiesTimeResponse,
ListEntitiesUpdateResponse,
ListEntitiesValveResponse,
LockCommandRequest,
LockStateResponse,
MediaPlayerCommandRequest,
MediaPlayerStateResponse,
NumberCommandRequest,
NumberStateResponse,
PingRequest,
PingResponse,
SelectCommandRequest,
SelectStateResponse,
SensorStateResponse,
SirenCommandRequest,
SirenStateResponse,
SubscribeBluetoothConnectionsFreeRequest,
SubscribeBluetoothLEAdvertisementsRequest,
SubscribeHomeassistantServicesRequest,
SubscribeHomeAssistantStateResponse,
SubscribeHomeAssistantStatesRequest,
SubscribeLogsRequest,
SubscribeLogsResponse,
SubscribeStatesRequest,
SubscribeVoiceAssistantRequest,
SwitchCommandRequest,
SwitchStateResponse,
TextCommandRequest,
TextSensorStateResponse,
TextStateResponse,
TimeCommandRequest,
TimeStateResponse,
UnsubscribeBluetoothLEAdvertisementsRequest,
UpdateCommandRequest,
UpdateStateResponse,
ValveCommandRequest,
ValveStateResponse,
VoiceAssistantAudio,
VoiceAssistantEventResponse,
VoiceAssistantRequest,
VoiceAssistantResponse,
VoiceAssistantTimerEventResponse,
)
TWO_CHAR = re.compile(r".{2}")
# Taken from esp_gatt_status_t in esp_gatt_defs.h
ESPHOME_GATT_ERRORS = {
-1: "Not connected", # Custom ESPHome error
1: "Invalid handle",
2: "Read not permitted",
3: "Write not permitted",
4: "Invalid PDU",
5: "Insufficient authentication",
6: "Request not supported",
7: "Invalid offset",
8: "Insufficient authorization",
9: "Prepare queue full",
10: "Attribute not found",
11: "Attribute not long",
12: "Insufficient key size",
13: "Invalid attribute length",
14: "Unlikely error",
15: "Insufficient encryption",
16: "Unsupported group type",
17: "Insufficient resources",
128: "Application error",
129: "Internal error",
130: "Wrong state",
131: "Database full",
132: "Busy",
133: "Error",
134: "Command started",
135: "Illegal parameter",
136: "Pending",
137: "Auth fail",
138: "More",
139: "Invalid configuration",
140: "Service started",
141: "Encrypted no mitm",
142: "Not encrypted",
143: "Congested",
144: "Duplicate registration",
145: "Already open",
146: "Cancel",
224: "Stack RSP",
225: "App RSP",
239: "Unknown error",
253: "CCC config error",
254: "Procedure already in progress",
255: "Out of range",
}
class APIConnectionError(Exception):
pass
class APIConnectionCancelledError(APIConnectionError):
pass
class InvalidAuthAPIError(APIConnectionError):
pass
class ResolveAPIError(APIConnectionError):
pass
class ProtocolAPIError(APIConnectionError):
pass
class RequiresEncryptionAPIError(ProtocolAPIError):
pass
class SocketAPIError(APIConnectionError):
pass
class SocketClosedAPIError(SocketAPIError):
pass
class HandshakeAPIError(APIConnectionError):
pass
class ConnectionNotEstablishedAPIError(APIConnectionError):
pass
class BadNameAPIError(APIConnectionError):
"""Raised when a name received from the remote but does not much the expected name."""
def __init__(self, msg: str, received_name: str) -> None:
super().__init__(f"{msg}: received_name={received_name}")
self.received_name = received_name
class InvalidEncryptionKeyAPIError(HandshakeAPIError):
def __init__(
self, msg: str | None = None, received_name: str | None = None
) -> None:
super().__init__(f"{msg}: received_name={received_name}")
self.received_name = received_name
class PingFailedAPIError(APIConnectionError):
pass
class TimeoutAPIError(APIConnectionError):
pass
class ReadFailedAPIError(APIConnectionError):
pass
class UnhandledAPIConnectionError(APIConnectionError):
pass
class BluetoothConnectionDroppedError(APIConnectionError):
"""Raised when a Bluetooth connection is dropped."""
def to_human_readable_address(address: int) -> str:
"""Convert a MAC address to a human readable format."""
return ":".join(TWO_CHAR.findall(f"{address:012X}"))
def to_human_readable_gatt_error(error: int) -> str:
"""Convert a GATT error to a human readable format."""
return ESPHOME_GATT_ERRORS.get(error, "Unknown error")
class BluetoothGATTAPIError(APIConnectionError):
def __init__(self, error: BluetoothGATTError) -> None:
super().__init__(
f"Bluetooth GATT Error "
f"address={to_human_readable_address(error.address)} "
f"handle={error.handle} "
f"error={error.error} "
f"description={to_human_readable_gatt_error(error.error)}"
)
self.error = error
MESSAGE_TYPE_TO_PROTO = {
1: HelloRequest,
2: HelloResponse,
3: ConnectRequest,
4: ConnectResponse,
5: DisconnectRequest,
6: DisconnectResponse,
7: PingRequest,
8: PingResponse,
9: DeviceInfoRequest,
10: DeviceInfoResponse,
11: ListEntitiesRequest,
12: ListEntitiesBinarySensorResponse,
13: ListEntitiesCoverResponse,
14: ListEntitiesFanResponse,
15: ListEntitiesLightResponse,
16: ListEntitiesSensorResponse,
17: ListEntitiesSwitchResponse,
18: ListEntitiesTextSensorResponse,
19: ListEntitiesDoneResponse,
20: SubscribeStatesRequest,
21: BinarySensorStateResponse,
22: CoverStateResponse,
23: FanStateResponse,
24: LightStateResponse,
25: SensorStateResponse,
26: SwitchStateResponse,
27: TextSensorStateResponse,
28: SubscribeLogsRequest,
29: SubscribeLogsResponse,
30: CoverCommandRequest,
31: FanCommandRequest,
32: LightCommandRequest,
33: SwitchCommandRequest,
34: SubscribeHomeassistantServicesRequest,
35: HomeassistantServiceResponse,
36: GetTimeRequest,
37: GetTimeResponse,
38: SubscribeHomeAssistantStatesRequest,
39: SubscribeHomeAssistantStateResponse,
40: HomeAssistantStateResponse,
41: ListEntitiesServicesResponse,
42: ExecuteServiceRequest,
43: ListEntitiesCameraResponse,
44: CameraImageResponse,
45: CameraImageRequest,
46: ListEntitiesClimateResponse,
47: ClimateStateResponse,
48: ClimateCommandRequest,
49: ListEntitiesNumberResponse,
50: NumberStateResponse,
51: NumberCommandRequest,
52: ListEntitiesSelectResponse,
53: SelectStateResponse,
54: SelectCommandRequest,
55: ListEntitiesSirenResponse,
56: SirenStateResponse,
57: SirenCommandRequest,
58: ListEntitiesLockResponse,
59: LockStateResponse,
60: LockCommandRequest,
61: ListEntitiesButtonResponse,
62: ButtonCommandRequest,
63: ListEntitiesMediaPlayerResponse,
64: MediaPlayerStateResponse,
65: MediaPlayerCommandRequest,
66: SubscribeBluetoothLEAdvertisementsRequest,
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,
82: BluetoothGATTErrorResponse,
83: BluetoothGATTWriteResponse,
84: BluetoothGATTNotifyResponse,
85: BluetoothDevicePairingResponse,
86: BluetoothDeviceUnpairingResponse,
87: UnsubscribeBluetoothLEAdvertisementsRequest,
88: BluetoothDeviceClearCacheResponse,
89: SubscribeVoiceAssistantRequest,
90: VoiceAssistantRequest,
91: VoiceAssistantResponse,
92: VoiceAssistantEventResponse,
93: BluetoothLERawAdvertisementsResponse,
94: ListEntitiesAlarmControlPanelResponse,
95: AlarmControlPanelStateResponse,
96: AlarmControlPanelCommandRequest,
97: ListEntitiesTextResponse,
98: TextStateResponse,
99: TextCommandRequest,
100: ListEntitiesDateResponse,
101: DateStateResponse,
102: DateCommandRequest,
103: ListEntitiesTimeResponse,
104: TimeStateResponse,
105: TimeCommandRequest,
106: VoiceAssistantAudio,
107: ListEntitiesEventResponse,
108: EventResponse,
109: ListEntitiesValveResponse,
110: ValveStateResponse,
111: ValveCommandRequest,
112: ListEntitiesDateTimeResponse,
113: DateTimeStateResponse,
114: DateTimeCommandRequest,
115: VoiceAssistantTimerEventResponse,
116: ListEntitiesUpdateResponse,
117: UpdateStateResponse,
118: UpdateCommandRequest,
}