stronger typing
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -152,3 +152,5 @@ cython_debug/
|
|||||||
#.idea/
|
#.idea/
|
||||||
aes.py
|
aes.py
|
||||||
*.bak
|
*.bak
|
||||||
|
emu/PV_BLE_cover/PV_BLE_cover.ino
|
||||||
|
img/
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ from bleak.exc import BleakError
|
|||||||
from bleak.uuids import normalize_uuid_str
|
from bleak.uuids import normalize_uuid_str
|
||||||
from bleak_retry_connector import establish_connection
|
from bleak_retry_connector import establish_connection
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from cryptography.hazmat.primitives.ciphers.base import (
|
||||||
|
AEADDecryptionContext,
|
||||||
|
AEADEncryptionContext,
|
||||||
|
)
|
||||||
from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
||||||
|
|
||||||
from .const import LOGGER, TIMEOUT
|
from .const import LOGGER, TIMEOUT
|
||||||
@@ -25,6 +29,7 @@ ATTR_ACTIVITY: Final[str] = "activity"
|
|||||||
|
|
||||||
|
|
||||||
SHADE_TYPE: Final[dict[int, str]] = {
|
SHADE_TYPE: Final[dict[int, str]] = {
|
||||||
|
# up down only
|
||||||
1: "Designer Roller",
|
1: "Designer Roller",
|
||||||
4: "Roman",
|
4: "Roman",
|
||||||
5: "Bottom Up",
|
5: "Bottom Up",
|
||||||
@@ -38,6 +43,11 @@ SHADE_TYPE: Final[dict[int, str]] = {
|
|||||||
52: "Banded Shades",
|
52: "Banded Shades",
|
||||||
53: "Sonnette",
|
53: "Sonnette",
|
||||||
84: "Vignette",
|
84: "Vignette",
|
||||||
|
# top down bottom up
|
||||||
|
8: "Duette, Top Down Bottom Up",
|
||||||
|
9: "Duette DuoLite, Top Down Bottom Up",
|
||||||
|
33: "Duette Architella, Top Down Bottom Up",
|
||||||
|
47: "Pleated, Top Down Bottom Up",
|
||||||
}
|
}
|
||||||
|
|
||||||
OPEN_POSITION: Final[int] = 100
|
OPEN_POSITION: Final[int] = 100
|
||||||
@@ -91,11 +101,11 @@ class PowerViewBLE:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
self._data_event = asyncio.Event()
|
self._data_event = asyncio.Event()
|
||||||
self._data: bytearray
|
self._data: bytearray = bytearray()
|
||||||
self._info: PVDeviceInfo = PVDeviceInfo()
|
self._info: PVDeviceInfo = PVDeviceInfo()
|
||||||
self._cmd_lock: Final = asyncio.Lock()
|
self._cmd_lock: Final = asyncio.Lock()
|
||||||
self._cmd_next = None
|
self._cmd_next: tuple[ShadeCmd, bytes]
|
||||||
self._cipher: Final = (
|
self._cipher: Final[Cipher | None] = (
|
||||||
Cipher(algorithms.AES(home_key), modes.CTR(bytearray(16)))
|
Cipher(algorithms.AES(home_key), modes.CTR(bytearray(16)))
|
||||||
if len(home_key) == 16
|
if len(home_key) == 16
|
||||||
else None
|
else None
|
||||||
@@ -117,7 +127,7 @@ class PowerViewBLE:
|
|||||||
|
|
||||||
# general cmd: uint16_t cmd, uint8_t seqID, uint8_t data_len
|
# general cmd: uint16_t cmd, uint8_t seqID, uint8_t data_len
|
||||||
async def _cmd(
|
async def _cmd(
|
||||||
self, cmd: tuple[ShadeCmd, bytearray], disconnect: bool = True
|
self, cmd: tuple[ShadeCmd, bytes], disconnect: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
self._cmd_next = cmd
|
self._cmd_next = cmd
|
||||||
if self._cmd_lock.locked():
|
if self._cmd_lock.locked():
|
||||||
@@ -127,16 +137,14 @@ class PowerViewBLE:
|
|||||||
async with self._cmd_lock:
|
async with self._cmd_lock:
|
||||||
try:
|
try:
|
||||||
await self._connect()
|
await self._connect()
|
||||||
cmd_run = self._cmd_next
|
cmd_run: tuple[ShadeCmd, bytes] = self._cmd_next
|
||||||
tx_data = (
|
tx_data: bytes = bytes(
|
||||||
bytearray(
|
int.to_bytes(cmd_run[0].value, 2, byteorder="little")
|
||||||
int.to_bytes(cmd_run[0].value, 2, byteorder="little")
|
+ bytes([self._seqcnt, len(cmd_run[1])])
|
||||||
+ bytes([self._seqcnt, len(cmd_run[1])])
|
|
||||||
)
|
|
||||||
+ cmd_run[1]
|
+ cmd_run[1]
|
||||||
)
|
)
|
||||||
if self._cipher is not None:
|
if self._cipher is not None:
|
||||||
enc = self._cipher.encryptor()
|
enc: AEADEncryptionContext = self._cipher.encryptor()
|
||||||
tx_data = enc.update(tx_data) + enc.finalize()
|
tx_data = enc.update(tx_data) + enc.finalize()
|
||||||
self._data_event.clear()
|
self._data_event.clear()
|
||||||
LOGGER.debug("sending cmd: %s", tx_data)
|
LOGGER.debug("sending cmd: %s", tx_data)
|
||||||
@@ -161,8 +169,8 @@ class PowerViewBLE:
|
|||||||
if len(data) != 9:
|
if len(data) != 9:
|
||||||
LOGGER.debug("not a V2 record!")
|
LOGGER.debug("not a V2 record!")
|
||||||
return []
|
return []
|
||||||
pos = int.from_bytes(data[3:5], byteorder="little")
|
pos: int = int.from_bytes(data[3:5], byteorder="little")
|
||||||
pos2 = (int(data[5]) << 4) + (int(data[4]) >> 4)
|
pos2: int = (int(data[5]) << 4) + (int(data[4]) >> 4)
|
||||||
return [
|
return [
|
||||||
(ATTR_CURRENT_POSITION, ((pos >> 2) / 10)),
|
(ATTR_CURRENT_POSITION, ((pos >> 2) / 10)),
|
||||||
("position2", pos2 >> 2),
|
("position2", pos2 >> 2),
|
||||||
@@ -185,7 +193,7 @@ class PowerViewBLE:
|
|||||||
await self._cmd(
|
await self._cmd(
|
||||||
(
|
(
|
||||||
ShadeCmd.SET_POSITION,
|
ShadeCmd.SET_POSITION,
|
||||||
bytearray(
|
bytes(
|
||||||
int.to_bytes(value * 100, 2, byteorder="little")
|
int.to_bytes(value * 100, 2, byteorder="little")
|
||||||
+ bytes([0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x0])
|
+ bytes([0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x0])
|
||||||
),
|
),
|
||||||
@@ -201,7 +209,7 @@ class PowerViewBLE:
|
|||||||
async def stop(self) -> None:
|
async def stop(self) -> None:
|
||||||
"""Stop device movement."""
|
"""Stop device movement."""
|
||||||
LOGGER.debug("%s stop", self.name)
|
LOGGER.debug("%s stop", self.name)
|
||||||
await self._cmd((ShadeCmd.STOP, bytearray(b"")))
|
await self._cmd((ShadeCmd.STOP, b""))
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
"""Fully close cover."""
|
"""Fully close cover."""
|
||||||
@@ -217,7 +225,7 @@ class PowerViewBLE:
|
|||||||
await self._cmd(
|
await self._cmd(
|
||||||
(
|
(
|
||||||
ShadeCmd.ACTIVATE_SCENE,
|
ShadeCmd.ACTIVATE_SCENE,
|
||||||
bytearray(int.to_bytes(idx, 1, byteorder="little") + bytes([0xA2])),
|
int.to_bytes(idx, 1, byteorder="little") + bytes([0xA2]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -225,7 +233,7 @@ class PowerViewBLE:
|
|||||||
"""Verify shade response data."""
|
"""Verify shade response data."""
|
||||||
data: bytearray = din
|
data: bytearray = din
|
||||||
if self._cipher is not None:
|
if self._cipher is not None:
|
||||||
dec = self._cipher.decryptor()
|
dec: AEADDecryptionContext = self._cipher.decryptor()
|
||||||
data = bytearray(dec.update(din) + dec.finalize())
|
data = bytearray(dec.update(din) + dec.finalize())
|
||||||
if len(data) < 4:
|
if len(data) < 4:
|
||||||
LOGGER.error("Reponse message too short")
|
LOGGER.error("Reponse message too short")
|
||||||
|
|||||||
Reference in New Issue
Block a user