Merge branch 'main' into main
This commit is contained in:
19
.github/workflows/_validate.bak
vendored
Normal file
19
.github/workflows/_validate.bak
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Validate with HACS
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 5 * * 6'
|
||||
|
||||
jobs:
|
||||
validate-hacs:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4"
|
||||
- name: HACS validation
|
||||
uses: "hacs/action@main"
|
||||
with:
|
||||
category: "integration"
|
||||
|
||||
16
.github/workflows/hassfest.yaml
vendored
Normal file
16
.github/workflows/hassfest.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Validate with hassfest
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * 6'
|
||||
|
||||
jobs:
|
||||
validate-hassfest:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4"
|
||||
- name: HA validation
|
||||
uses: "home-assistant/actions/hassfest@master"
|
||||
28
.github/workflows/lint.yml
vendored
Normal file
28
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: "Lint the code"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 5 * * 6'
|
||||
|
||||
jobs:
|
||||
ruff:
|
||||
name: "Ruff"
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Checkout the repository"
|
||||
uses: "actions/checkout@main"
|
||||
|
||||
- name: "Set up Python"
|
||||
uses: actions/setup-python@main
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: "pip"
|
||||
|
||||
- name: "Install requirements"
|
||||
run: python3 -m pip install -r requirements.txt
|
||||
|
||||
- name: "Run Ruff"
|
||||
run: python3 -m ruff check .
|
||||
22
.github/workflows/stale.yaml
vendored
Normal file
22
.github/workflows/stale.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: 'Close stale issues and PR'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '01 2 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 8 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 15 days with no activity.'
|
||||
exempt-issue-labels: 'bug'
|
||||
exempt-all-assignees: true
|
||||
days-before-stale: 45
|
||||
days-before-close: 15
|
||||
days-before-pr-close: -1
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -150,3 +150,5 @@ cython_debug/
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
aes.py
|
||||
*.bak
|
||||
|
||||
24
README.md
24
README.md
@@ -5,11 +5,10 @@
|
||||
|
||||
A Home Assistant integration to support Hunter Douglas Powerview devices via Bluetooth
|
||||
|
||||
## :warning: Limitations
|
||||
- This integration is under development!
|
||||
- Test coverage is low, malfunction might occur.
|
||||
- Only devices that are **not** added to the app are controlable. It is possible to add them to the app if you just want to monitor the status (position, battery) in Home Assistant.
|
||||
- Currently only position change is supported (e.g., no tilt)
|
||||
> [!WARNING]
|
||||
> - This integration is under development!
|
||||
> - Test coverage is low, malfunction might occur.
|
||||
> - Currently only position change is supported (e.g., no tilt)
|
||||
|
||||
## Features
|
||||
- Zero configuration
|
||||
@@ -39,10 +38,14 @@ The integration provides the following information about the battery
|
||||
Platform | Description | Unit | Details
|
||||
-- | -- | -- | --
|
||||
`binary_sensor` | battery charging indicator | `bool` | true if battery is charging
|
||||
`button` | identify shade | - | identify shade by LED and 3 beeps
|
||||
`cover` | view/control position | `%` | percentage cover is open (100% is open)
|
||||
`sensor` | SoC (state of charge) | `%` | range 100% (full), 50%, 20%, 0% (battery empty)
|
||||
|
||||
## Installation
|
||||
> [!IMPORTANT]
|
||||
> In case you added your shades to the app or a gateway, you need to [set the encryption key](#set-the-encryption-key) manually in the [`const.py`](https://github.com/patman15/hdpv_ble/blob/main/custom_components/hunterdouglas_powerview_ble/const.py) file after **each** update!
|
||||
|
||||
### Automatic
|
||||
Installation can be done using [HACS](https://hacs.xyz/) by [adding a custom repository](https://hacs.xyz/docs/faq/custom_repositories/).
|
||||
|
||||
@@ -57,6 +60,17 @@ Installation can be done using [HACS](https://hacs.xyz/) by [adding a custom rep
|
||||
1. Restart Home Assistant
|
||||
1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Hunter Douglas PowerView (BLE)"
|
||||
|
||||
## Set the Encryption Key
|
||||
Currently, there are three methods to optain the key:
|
||||
|
||||
1. Via adopting a BLE shade: There is a [shade emulator](/emu/PV_BLE_cover) that works with Arduino IDE and an ESP32 device (≥ 2MiB flash, ≥ 128KiB required), e.g. [Adafruit QT Py ESP32-S3](https://www.adafruit.com/product/5426). Install and connect via serial port, then go to the PowerView app and add the shade `myPVcover` to your home. You will see a log message `set shade key: \xx\xx\xx\xx\xx\xx\xx\xx\xx\xx\xx\xx\xx\xx\xx\xx` . Copy this key. You can delete the shade from the app when done.
|
||||
2. Extracting from gateway: This [PR](https://github.com/patman15/hdpv_ble/pull/2) proposes a script to extract the key from a working PowerView gateway.
|
||||
3. Grabing from the app: Checkout this [post in the Home Assistant community forum](https://community.home-assistant.io/t/hunter-douglas-powerview-gen-3-integration/424836/228).
|
||||
|
||||
Finally, you need to manually copy the key to [`const.py`](https://github.com/patman15/hdpv_ble/blob/main/custom_components/hunterdouglas_powerview_ble/const.py).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> You need to update the file after **each** update!
|
||||
|
||||
## Outlook
|
||||
- Add support for encryption
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"""
|
||||
|
||||
from bleak.exc import BleakError
|
||||
|
||||
from homeassistant.components.bluetooth import async_ble_device_from_address
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
@@ -15,7 +14,7 @@ from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import PVCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.COVER, Platform.SENSOR]
|
||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.COVER, Platform.SENSOR, Platform.BUTTON]
|
||||
|
||||
type ConfigEntryType = ConfigEntry[PVCoordinator]
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
"""Hunter Douglas PowerView BLE API."""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import time
|
||||
from typing import Final
|
||||
|
||||
from bleak import BleakClient
|
||||
from bleak.backends.device import BLEDevice
|
||||
from bleak.exc import BleakError
|
||||
from bleak.uuids import normalize_uuid_str
|
||||
from bleak_retry_connector import close_stale_connections, establish_connection
|
||||
from bleak_retry_connector import establish_connection
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers.base import CipherContext
|
||||
from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
||||
|
||||
from .const import LOGGER, TIMEOUT
|
||||
|
||||
UUID_COV_SERVICE: Final = normalize_uuid_str("fdc1")
|
||||
UUID_TX: Final = "cafe1001-c0ff-ee01-8000-a110ca7ab1e0"
|
||||
UUID_DEV_SERVICE: Final = normalize_uuid_str("180a")
|
||||
UUID_BAT_SERVICE: Final = normalize_uuid_str("180f")
|
||||
UUID_COV_SERVICE: Final[str] = normalize_uuid_str("fdc1")
|
||||
UUID_TX: Final[str] = "cafe1001-c0ff-ee01-8000-a110ca7ab1e0"
|
||||
UUID_DEV_SERVICE: Final[str] = normalize_uuid_str("180a")
|
||||
UUID_BAT_SERVICE: Final[str] = normalize_uuid_str("180f")
|
||||
|
||||
ATTR_ACTIVITY: Final = "activity"
|
||||
ATTR_ACTIVITY: Final[str] = "activity"
|
||||
|
||||
|
||||
SHADE_TYPE: Final[dict[int, str]] = {
|
||||
@@ -59,6 +59,7 @@ class ShadeCmd(Enum):
|
||||
SET_POSITION = 0x01F7
|
||||
STOP = 0xB8F7
|
||||
ACTIVATE_SCENE = 0xBAF7
|
||||
IDENTIFY = 0x11F7
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -82,12 +83,21 @@ class PowerViewBLE:
|
||||
self._ble_device: Final[BLEDevice] = ble_device
|
||||
self.name: Final[str] = self._ble_device.name or "unknown"
|
||||
self._seqcnt: int = 1
|
||||
self._client: BleakClient | None = None
|
||||
self._client: BleakClient = BleakClient(
|
||||
self._ble_device,
|
||||
disconnected_callback=self._on_disconnect,
|
||||
services=[
|
||||
UUID_COV_SERVICE,
|
||||
# self.UUID_DEV_SERVICE,
|
||||
# self.UUID_BAT_SERVICE,
|
||||
],
|
||||
)
|
||||
self._data_event = asyncio.Event()
|
||||
self._data: bytearray
|
||||
self._info: PVDeviceInfo = PVDeviceInfo()
|
||||
self._cmd_lock: Final = asyncio.Lock()
|
||||
self._cmd_next = None
|
||||
self._is_encrypted: bool = False
|
||||
self._cipher: Final = (
|
||||
Cipher(algorithms.AES(home_key), modes.CTR(bytearray(16)))
|
||||
if len(home_key) == 16
|
||||
@@ -98,6 +108,15 @@ class PowerViewBLE:
|
||||
await self._data_event.wait()
|
||||
self._data_event.clear()
|
||||
|
||||
@property
|
||||
def encrypted(self) -> bool:
|
||||
"""Return whether communication with this shade is encrypted."""
|
||||
return self._is_encrypted
|
||||
|
||||
@encrypted.setter
|
||||
def encrypted(self, value: bool) -> None:
|
||||
self._is_encrypted = value
|
||||
|
||||
@property
|
||||
def info(self) -> PVDeviceInfo:
|
||||
"""Return device information, e.g. SW version."""
|
||||
@@ -106,7 +125,7 @@ class PowerViewBLE:
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
"""Return whether remote device is connected."""
|
||||
return self._client is not None and self._client.is_connected
|
||||
return self._client.is_connected
|
||||
|
||||
# general cmd: uint16_t cmd, uint8_t seqID, uint8_t data_len
|
||||
async def _cmd(
|
||||
@@ -120,7 +139,6 @@ class PowerViewBLE:
|
||||
async with self._cmd_lock:
|
||||
try:
|
||||
await self._connect()
|
||||
assert self._client is not None, "missing BT client"
|
||||
cmd_run = self._cmd_next
|
||||
tx_data = (
|
||||
bytearray(
|
||||
@@ -129,11 +147,12 @@ class PowerViewBLE:
|
||||
)
|
||||
+ cmd_run[1]
|
||||
)
|
||||
if self._cipher is not None:
|
||||
LOGGER.debug("sending cmd: %s", tx_data.hex(" "))
|
||||
if self._cipher is not None and self._is_encrypted:
|
||||
enc = self._cipher.encryptor()
|
||||
tx_data = enc.update(tx_data) + enc.finalize()
|
||||
LOGGER.debug(" encrypted: %s", tx_data.hex(" "))
|
||||
self._data_event.clear()
|
||||
LOGGER.debug("sending cmd: %s", tx_data)
|
||||
await self._client.write_gatt_char(UUID_TX, tx_data, False)
|
||||
self._seqcnt += 1
|
||||
LOGGER.debug("waiting for response")
|
||||
@@ -156,14 +175,14 @@ class PowerViewBLE:
|
||||
LOGGER.debug("not a V2 record!")
|
||||
return []
|
||||
pos = int.from_bytes(data[3:5], byteorder="little")
|
||||
pos2 = ((int(data[5]) << 4) + (int(data[4]) >> 4))
|
||||
pos2 = (int(data[5]) << 4) + (int(data[4]) >> 4)
|
||||
return [
|
||||
(ATTR_CURRENT_POSITION, ((pos >> 2) / 10)),
|
||||
("position2", pos2 >> 2),
|
||||
("position3", int(data[6])),
|
||||
("tilt", int(data[7])),
|
||||
("home_id", int.from_bytes(data[0:2], byteorder="little")),
|
||||
("type_id", int.from_bytes(data[2:3])),
|
||||
("type_id", int(data[2])),
|
||||
("is_opening", bool(pos & 0x3 == 0x2)),
|
||||
("is_closing", bool(pos & 0x3 == 0x1)),
|
||||
("battery_charging", bool(pos & 0x3 == 0x3)), # observed
|
||||
@@ -195,7 +214,7 @@ class PowerViewBLE:
|
||||
async def stop(self) -> None:
|
||||
"""Stop device movement."""
|
||||
LOGGER.debug("%s stop", self.name)
|
||||
await self._cmd((ShadeCmd.STOP, bytearray(b"")))
|
||||
await self._cmd((ShadeCmd.STOP, bytearray()))
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Fully close cover."""
|
||||
@@ -215,12 +234,13 @@ class PowerViewBLE:
|
||||
),
|
||||
)
|
||||
|
||||
def _verify_response(self, input: bytearray, seq_nr: int, cmd: ShadeCmd) -> bool:
|
||||
async def identify(self, beeps: int = 0x3) -> None:
|
||||
"""Identify device."""
|
||||
LOGGER.debug("%s identify (%i)", self.name, beeps)
|
||||
await self._cmd((ShadeCmd.IDENTIFY, bytearray([min(beeps, 0xFF)])))
|
||||
|
||||
def _verify_response(self, data: bytearray, seq_nr: int, cmd: ShadeCmd) -> bool:
|
||||
"""Verify shade response data."""
|
||||
data = input
|
||||
if self._cipher is not None:
|
||||
dec = self._cipher.decryptor()
|
||||
data = dec.update(input) + dec.finalize()
|
||||
if len(data) < 4:
|
||||
LOGGER.error("Reponse message too short")
|
||||
return False
|
||||
@@ -229,14 +249,14 @@ class PowerViewBLE:
|
||||
return False
|
||||
if int(data[2]) != seq_nr:
|
||||
LOGGER.warning(
|
||||
f"Response sequence id {int(data[2])} wrong, expected {seq_nr}"
|
||||
"Response sequence id %i wrong, expected %d", int(data[2]), seq_nr
|
||||
)
|
||||
return False
|
||||
if int(data[3]) != 1:
|
||||
LOGGER.error("Wrong response data length")
|
||||
return False
|
||||
if int(data[4] != 0):
|
||||
LOGGER.error(f"Command {cmd.value} returned error #{int(data[4])}")
|
||||
LOGGER.error("Command %X returned error #%d", cmd.value, int(data[4]))
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -255,7 +275,6 @@ class PowerViewBLE:
|
||||
async with self._cmd_lock:
|
||||
try:
|
||||
await self._connect()
|
||||
assert self._client is not None
|
||||
|
||||
for key, uuid in uuids.items():
|
||||
LOGGER.debug("querying %s(%s)", key, uuid)
|
||||
@@ -265,7 +284,7 @@ class PowerViewBLE:
|
||||
.decode("UTF-8")
|
||||
)
|
||||
finally:
|
||||
await self._disconnect()
|
||||
await self.disconnect()
|
||||
LOGGER.debug("%s device data: %s", self.name, data)
|
||||
return data.copy()
|
||||
|
||||
@@ -275,8 +294,13 @@ class PowerViewBLE:
|
||||
LOGGER.debug("Disconnected from %s", client.address)
|
||||
|
||||
def _notification_handler(self, _sender, data: bytearray) -> None:
|
||||
LOGGER.debug("%s received BLE data: %s", self.name, data)
|
||||
LOGGER.debug("%s received BLE data: %s", self.name, data.hex(" "))
|
||||
self._data = data
|
||||
if self._cipher is not None and self._is_encrypted:
|
||||
dec: CipherContext = self._cipher.decryptor()
|
||||
self._data = bytearray(dec.update(data) + dec.finalize())
|
||||
LOGGER.debug("%s %s", "decoded data: ".rjust(19+len(self.name)), self._data.hex(" "))
|
||||
|
||||
self._data_event.set()
|
||||
|
||||
async def _connect(self) -> None:
|
||||
@@ -288,9 +312,7 @@ class PowerViewBLE:
|
||||
LOGGER.debug("%s already connected", self.name)
|
||||
return
|
||||
|
||||
start = time.time()
|
||||
await close_stale_connections(self._ble_device)
|
||||
|
||||
start: float = time.time()
|
||||
self._client = await establish_connection(
|
||||
BleakClient,
|
||||
self._ble_device,
|
||||
@@ -308,10 +330,10 @@ class PowerViewBLE:
|
||||
|
||||
# await self._query_dev_info()
|
||||
|
||||
async def _disconnect(self) -> None:
|
||||
async def disconnect(self) -> None:
|
||||
"""Disconnect the device and stop notifications."""
|
||||
|
||||
if self._client is not None and self.is_connected:
|
||||
if self.is_connected:
|
||||
LOGGER.debug("Disconnecting device %s", self.name)
|
||||
try:
|
||||
self._data_event.clear()
|
||||
|
||||
71
custom_components/hunterdouglas_powerview_ble/button.py
Normal file
71
custom_components/hunterdouglas_powerview_ble/button.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Hunter Douglas Powerview cover."""
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.bluetooth.passive_update_coordinator import (
|
||||
PassiveBluetoothCoordinatorEntity,
|
||||
)
|
||||
from homeassistant.components.button import (
|
||||
ButtonDeviceClass,
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo, format_mac
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import PVCoordinator
|
||||
|
||||
BUTTONS_SHADE: Final = [
|
||||
ButtonEntityDescription(
|
||||
key="identify",
|
||||
device_class=ButtonDeviceClass.IDENTIFY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
_hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the demo cover platform."""
|
||||
|
||||
coordinator: PVCoordinator = config_entry.runtime_data
|
||||
for descr in BUTTONS_SHADE:
|
||||
async_add_entities([PowerViewButton(coordinator, descr)])
|
||||
|
||||
|
||||
class PowerViewButton(PassiveBluetoothCoordinatorEntity[PVCoordinator], ButtonEntity): # type: ignore[reportIncompatibleVariableOverride]
|
||||
"""Representation of a powerview shade."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_device_class = ButtonDeviceClass.IDENTIFY
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: PVCoordinator,
|
||||
description: ButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the shade."""
|
||||
self.entity_description = description
|
||||
self._coord: PVCoordinator = coordinator
|
||||
self._attr_device_info = self._coord.device_info
|
||||
self._attr_unique_id = (
|
||||
f"{DOMAIN}_{format_mac(self._coord.address)}_{ButtonDeviceClass.IDENTIFY}"
|
||||
)
|
||||
super().__init__(coordinator)
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo: # type: ignore[reportIncompatibleVariableOverride]
|
||||
"""Return the device_info of the device."""
|
||||
return self._coord.device_info
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
LOGGER.debug("identify cover")
|
||||
await self._coord.api.identify()
|
||||
@@ -4,7 +4,6 @@ from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.bluetooth import (
|
||||
BluetoothServiceInfoBleak,
|
||||
|
||||
@@ -13,12 +13,15 @@ from typing import Final
|
||||
# )
|
||||
|
||||
|
||||
DOMAIN: Final = "hunterdouglas_powerview_ble"
|
||||
DOMAIN: Final[str] = "hunterdouglas_powerview_ble"
|
||||
LOGGER: Final = logging.getLogger(__package__)
|
||||
MFCT_ID: Final = 2073
|
||||
TIMEOUT: Final = 5
|
||||
HOME_KEY: Final = b""
|
||||
MFCT_ID: Final[int] = 2073
|
||||
TIMEOUT: Final[int] = 5
|
||||
|
||||
# put the key here, needs to be 16 bytes long, e.g.
|
||||
# HOME_KEY: Final[bytes] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
|
||||
HOME_KEY: Final[bytes] = b""
|
||||
|
||||
|
||||
# attributes (do not change)
|
||||
ATTR_RSSI = "rssi"
|
||||
ATTR_RSSI: Final[str] = "rssi"
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
from typing import Any
|
||||
|
||||
from bleak.backends.device import BLEDevice
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.components.bluetooth import DOMAIN as BLUETOOTH_DOMAIN
|
||||
from homeassistant.components.bluetooth.const import DOMAIN as BLUETOOTH_DOMAIN
|
||||
from homeassistant.components.bluetooth.passive_update_coordinator import (
|
||||
PassiveBluetoothDataUpdateCoordinator,
|
||||
)
|
||||
@@ -79,6 +78,12 @@ class PVCoordinator(PassiveBluetoothDataUpdateCoordinator):
|
||||
"""Check if a device is present."""
|
||||
return bluetooth.async_address_present(self.hass, self._mac, connectable=True)
|
||||
|
||||
def _async_stop(self) -> None:
|
||||
"""Shutdown coordinator and any connection."""
|
||||
LOGGER.debug("%s: shuting down BMS device", self.name)
|
||||
self.hass.async_create_task(self.api.disconnect())
|
||||
super()._async_stop()
|
||||
|
||||
@callback
|
||||
def _async_handle_bluetooth_event(
|
||||
self,
|
||||
@@ -98,6 +103,7 @@ class PVCoordinator(PassiveBluetoothDataUpdateCoordinator):
|
||||
bytearray(service_info.manufacturer_data.get(2073, b""))
|
||||
)
|
||||
)
|
||||
self.api.encrypted = bool(self.data.get("home_id"))
|
||||
|
||||
LOGGER.debug("data sample %s", self.data)
|
||||
super()._async_handle_bluetooth_event(service_info, change)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from typing import Any, Final
|
||||
|
||||
from bleak.exc import BleakError
|
||||
|
||||
from homeassistant.components.bluetooth.passive_update_coordinator import (
|
||||
PassiveBluetoothCoordinatorEntity,
|
||||
)
|
||||
@@ -127,7 +126,10 @@ class PowerViewCover(PassiveBluetoothCoordinatorEntity[PVCoordinator], CoverEnti
|
||||
self.async_write_ha_state()
|
||||
except BleakError as err:
|
||||
LOGGER.error(
|
||||
f"Failed to move cover '{self.name}' to {target_position}%: {err}"
|
||||
"Failed to move cover '%s' to %f%%: %s",
|
||||
self.name,
|
||||
target_position,
|
||||
err,
|
||||
)
|
||||
|
||||
def _reset_target_position(self) -> None:
|
||||
@@ -143,7 +145,7 @@ class PowerViewCover(PassiveBluetoothCoordinatorEntity[PVCoordinator], CoverEnti
|
||||
await self._coord.api.open()
|
||||
self.async_write_ha_state()
|
||||
except BleakError as err:
|
||||
LOGGER.error(f"Failed to open cover '{self.name}': {err}")
|
||||
LOGGER.error("Failed to open cover '%s': %s", self.name, err)
|
||||
self._reset_target_position()
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
@@ -156,7 +158,7 @@ class PowerViewCover(PassiveBluetoothCoordinatorEntity[PVCoordinator], CoverEnti
|
||||
await self._coord.api.close()
|
||||
self.async_write_ha_state()
|
||||
except BleakError as err:
|
||||
LOGGER.error(f"Failed to close cover '{self.name}': {err}")
|
||||
LOGGER.error("Failed to close cover '%s': %s", self.name, err)
|
||||
self._reset_target_position()
|
||||
|
||||
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||
@@ -167,4 +169,4 @@ class PowerViewCover(PassiveBluetoothCoordinatorEntity[PVCoordinator], CoverEnti
|
||||
self._reset_target_position()
|
||||
self.async_write_ha_state()
|
||||
except BleakError as err:
|
||||
LOGGER.error(f"Failed to stop cover '{self.name}': {err}")
|
||||
LOGGER.error("Failed to stop cover '%s': %s", self.name, err)
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
"issue_tracker": "https://github.com/patman15/hdpv_ble/issues",
|
||||
"loggers": ["hunterdouglas_powerview_ble"],
|
||||
"requirements": ["cryptography>=43.0.0"],
|
||||
"version": 0.21
|
||||
"version": "0.22"
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ from homeassistant.components.bluetooth.passive_update_coordinator import (
|
||||
PassiveBluetoothCoordinatorEntity,
|
||||
)
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.components.sensor.const import (
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
|
||||
@@ -1,40 +1,58 @@
|
||||
/**
|
||||
* Emulate a Hunter Douglas PowerView cover device using ESP32
|
||||
* used e.g. to gain the home_key from an existing installation via BLE
|
||||
*
|
||||
* TODO:
|
||||
* - adding device to appartement does only work after long timeout,
|
||||
* as some feedback to "reset scene automations" is expected
|
||||
* - cleanup code
|
||||
* - think about emulating a remote
|
||||
*
|
||||
* AUTHOR: patman15
|
||||
* LICENSE: GPLv2
|
||||
*/
|
||||
|
||||
#define NAME "myPVcover"
|
||||
#define FW_VERSION "391"
|
||||
#define SERIAL_NR "01234567890ABCDEF"
|
||||
|
||||
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <BLE2902.h>
|
||||
|
||||
#define WOLFSSL_USER_SETTINGS
|
||||
#include <wolfssl.h>
|
||||
#include "wolfssl/wolfcrypt/aes.h"
|
||||
|
||||
Aes aes_coder;
|
||||
void *hint = NULL;
|
||||
int devId = INVALID_DEVID; //if not using async INVALID_DEVID is default
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define NAME "myPVcover"
|
||||
|
||||
#define COVER_SERVICE_UUID "0000FDC1-0000-1000-8000-00805f9b34fb"
|
||||
#define COVER_CHAR_UUID "CAFE1001-C0FF-EE01-8000-A110CA7AB1E0"
|
||||
#define XXX_CHAR_UUID "CAFE1002-C0FF-EE01-8000-A110CA7AB1E0"
|
||||
|
||||
//#define FW_SERVICE_UUID "CAFE8000-C0FF-EE01-8000-A110CA7AB1E0"
|
||||
#define FW_SERVICE_UUID "CAFE8000-C0FF-EE01-8000-A110CA7AB1E0"
|
||||
#define FW_CHAR_UUID "CAFE8003-C0FF-EE01-8000-A110CA7AB1E0"
|
||||
|
||||
#define DEV_SERVICE_UUID BLEUUID("180A")
|
||||
#define SER_CHAR_UUID BLEUUID("2A25")
|
||||
#define SWC_CHAR_UUID BLEUUID("2A28")
|
||||
|
||||
#define BAT_SERVICE_UUID BLEUUID("180F")
|
||||
#define BAT_CHAR_UUID BLEUUID("2A19")
|
||||
|
||||
|
||||
#define DAT_LEN 255
|
||||
#pragma pack(1)
|
||||
struct header {
|
||||
struct message {
|
||||
uint8_t serviceID;
|
||||
uint8_t cmdID;
|
||||
uint8_t sequence;
|
||||
uint8_t data_len;
|
||||
uint8_t data[DAT_LEN];
|
||||
};
|
||||
|
||||
struct position {
|
||||
@@ -45,151 +63,280 @@ struct position {
|
||||
uint8_t velocity;
|
||||
};
|
||||
|
||||
struct notification {
|
||||
uint8_t *data;
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
BLECharacteristic *pCharacteristic_cover, *pCharacteristic_fw, *pCharacteristic_unknown, *pCharacteristic_bat;
|
||||
BLECharacteristic *pCharacteristic_dev, *pCharacteristic_ser;
|
||||
BLEServer *pServer = NULL;
|
||||
bool deviceConnected = false;
|
||||
bool oldDeviceConnected = false;
|
||||
struct notification rx_data;
|
||||
volatile bool data_available = false;
|
||||
const byte zero_key[16] = { 0 };
|
||||
byte home_key[16] = { 0 };
|
||||
|
||||
void Serialprintln(const char* input...) {
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
for(const char* i=input; *i!=0; ++i) {
|
||||
if(*i!='%') { Serial.print(*i); continue; }
|
||||
switch(*(++i)) {
|
||||
case '%': Serial.print('%'); break;
|
||||
case 's': Serial.print(va_arg(args, char*)); break;
|
||||
case 'd': Serial.print(va_arg(args, int), DEC); break;
|
||||
case 'b': Serial.print(va_arg(args, int), BIN); break;
|
||||
case 'x': Serial.print(va_arg(args, int), HEX); break;
|
||||
case 'f': Serial.print(va_arg(args, double), 2); break;
|
||||
}
|
||||
|
||||
const char *BLEstate[] = {
|
||||
"SUCCESS_INDICATE",
|
||||
"SUCCESS_NOTIFY",
|
||||
"ERROR_INDICATE_DISABLED",
|
||||
"ERROR_NOTIFY_DISABLED",
|
||||
"ERROR_GATT",
|
||||
"ERROR_NO_CLIENT",
|
||||
"ERROR_INDICATE_TIMEOUT",
|
||||
"ERROR_INDICATE_FAILURE"
|
||||
};
|
||||
|
||||
void print_hex(const uint8_t *value, uint8_t len, const char *prefix = "0x", const char *postfix = " ") {
|
||||
for (int i = 0; i < len; i++) {
|
||||
Serial.printf("%s%02X%s", prefix, value[i], postfix);
|
||||
}
|
||||
Serial.println();
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
const char* decode_cmd(uint16_t cmd) {
|
||||
switch(cmd) {
|
||||
case 0x01:
|
||||
return "set position";
|
||||
case 0xBA:
|
||||
return "activate scene";
|
||||
uint8_t set_response(message *response, const message *request, const byte *data = NULL, const uint8_t data_len = 1) {
|
||||
const uint8_t message_len = min(data_len, (uint8_t)DAT_LEN) + sizeof(struct message) - DAT_LEN;
|
||||
response->serviceID = request->serviceID & 0xEF;
|
||||
response->cmdID = request->cmdID;
|
||||
response->sequence = request->sequence;
|
||||
response->data_len = min(data_len, (uint8_t)DAT_LEN);
|
||||
if (data) {
|
||||
memcpy(response->data, data, std::min(data_len, (uint8_t)DAT_LEN));
|
||||
} else {
|
||||
*response->data = 0x0;
|
||||
}
|
||||
Serial.printf("\tret value (%i): ", message_len);
|
||||
print_hex((const uint8_t *)response, message_len);
|
||||
if (memcmp(home_key, zero_key, sizeof(zero_key))) {
|
||||
message unencrypted;
|
||||
memcpy(&unencrypted, response, message_len);
|
||||
// AES counter is reset every message, so we need to init it each time
|
||||
if (wc_AesInit(&aes_coder, hint, devId) || wc_AesSetKey(&aes_coder, (const byte *)home_key, 16, zero_key, AES_ENCRYPTION)) {
|
||||
Serial.println("FATAL: setting AES init failed!");
|
||||
return 0;
|
||||
}
|
||||
if (wc_AesCtrEncrypt(&aes_coder, (byte *)response, (const byte *)&unencrypted, message_len)) {
|
||||
Serial.println(F("FATAL: encryption failed!"));
|
||||
return 0;
|
||||
}
|
||||
Serial.printf("\tencrypted (%i): ", message_len);
|
||||
print_hex((const uint8_t *)response, message_len);
|
||||
}
|
||||
return message_len;
|
||||
}
|
||||
|
||||
void decode(BLECharacteristic *pChar) {
|
||||
message response;
|
||||
byte data_dec[DAT_LEN];
|
||||
const uint16_t data_len = pChar->getLength();
|
||||
const byte *data_raw = pChar->getData();
|
||||
struct message msg;
|
||||
uint8_t resp_size = 0;
|
||||
|
||||
Serial.print("\t BLE data: ");
|
||||
print_hex(data_raw, data_len);
|
||||
|
||||
if (data_len < 4) return;
|
||||
|
||||
if (memcmp(home_key, zero_key, sizeof(zero_key))) {
|
||||
if (wc_AesInit(&aes_coder, hint, devId) || wc_AesSetKey(&aes_coder, (const byte *)home_key, 16, zero_key, AES_ENCRYPTION)) {
|
||||
Serial.println("FATAL: setting AES init failed!");
|
||||
}
|
||||
if (wc_AesCtrEncrypt(&aes_coder, data_dec, data_raw, data_len)) {
|
||||
Serial.println(F("FATAL: decryption failed!"));
|
||||
return;
|
||||
}
|
||||
Serial.print("\tdecrypted: ");
|
||||
print_hex(data_dec, data_len);
|
||||
} else {
|
||||
memcpy(data_dec, data_raw, data_len);
|
||||
}
|
||||
|
||||
memcpy((void *)&msg, data_dec, 4);
|
||||
Serial.printf("\t message: SRV: %02x, CMD %02x, SEQ %i, LEN %i\n", msg.serviceID, msg.cmdID, msg.sequence, msg.data_len);
|
||||
|
||||
// sepecial responses (static data!)
|
||||
const byte ret_valF1DD[] = { 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x87, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // product info
|
||||
const byte ret_valFFDD[] = { 0x00, 0x05, 0xd1, 0xa2, 0x9a, 0x42, 0x59, 0x5d, 0x5c, 0x52, 0x1b, 0x00, 0x00, 0x00, 0x87, 0x01, 0x00, 0x00, 0x5f, 0x9c, 0x02, 0x00, 0x5f, 0x9c, 0x02, 0x00, 0x2a, 0xe0, 0x08 }; // HW diagnostics
|
||||
const byte ret_valFFDE[] = { 0x08, 0x00, 0x02, 0x26, 0x72, 0x01, 0x59, 0x01, 0x00 }; // power status
|
||||
const byte ret_valFA5B[] = { 0x00, 0x0a, 0xa2, 0x88, 0x13, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // get scene
|
||||
const byte ret_valFA5A[] = { 0x00, 0x02, 0xb0 }; // set scene
|
||||
|
||||
Serial.print("\t\t");
|
||||
switch ((msg.serviceID << 8) | msg.cmdID) {
|
||||
case 0xF1DD:
|
||||
Serial.println("get product info.");
|
||||
resp_size = set_response(&response, (const message *)data_dec, ret_valF1DD, sizeof(ret_valF1DD));
|
||||
break;
|
||||
case 0xF701:
|
||||
// set position
|
||||
struct position pos;
|
||||
memcpy((void *)&pos, &data_dec[4], msg.data_len);
|
||||
Serial.printf("set position: pos1 %f%%, pos2 %d, pos3 %d, tilt %d, velocity %d\n", pos.pos1 / 100.0, pos.pos2, pos.pos3, pos.tilt, pos.velocity);
|
||||
break;
|
||||
case 0xF711:
|
||||
// identify
|
||||
Serial.printf("identify: %i times\n", data_dec[4]);
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
case 0xF7B8:
|
||||
// stop movement
|
||||
Serial.println("stop.");
|
||||
break;
|
||||
case 0xF7BA:
|
||||
// activate scene
|
||||
Serial.printf("activate scene #%i\n", data_dec[4]);
|
||||
break;
|
||||
case 0xFA5A:
|
||||
// set scene
|
||||
Serial.printf("set scene #%i\n", data_dec[4]);
|
||||
resp_size = set_response(&response, (const message *)data_dec, ret_valFA5A, sizeof(ret_valFA5A));
|
||||
break;
|
||||
case 0xFA5B:
|
||||
// get scene
|
||||
Serial.printf("get scene #%i\n", data_dec[4]);
|
||||
resp_size = set_response(&response, (const message *)data_dec, ret_valFA5B, sizeof(ret_valFA5B));
|
||||
break;
|
||||
case 0xFAEA:
|
||||
// Reset Scene Automations
|
||||
Serial.println("reset scene automations:");
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
case 0xFB02:
|
||||
// set shade key
|
||||
Serial.print("set shade key: ");
|
||||
print_hex(&data_raw[4], data_len - 4, "\\x", "");
|
||||
// set resonse before key, to acknowledge unencrypted
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
if (msg.data_len == 16) {
|
||||
memcpy(home_key, &data_raw[4], 16);
|
||||
}
|
||||
break;
|
||||
// case 0xFF67:
|
||||
// // get shade time
|
||||
// break;
|
||||
case 0xFF77:
|
||||
// set shade time
|
||||
Serial.printf("set time: %i-%i-%i %i:%i:%i\n", data_dec[4] | data_dec[5] << 8, data_dec[6], data_dec[7], data_dec[8], data_dec[9], data_dec[10]);
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
case 0xFF87:
|
||||
Serial.printf("set sunrise %i:%i:%i, sunset %i:%i:%i\n", data_dec[4], data_dec[5], data_dec[6], data_dec[7], data_dec[8], data_dec[9]);
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
case 0xFFD7:
|
||||
Serial.printf("set shade configuration: 0x%02X, status LED: %s\n", data_dec[4], data_dec[5] ? "on" : "off");
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
case 0xFFDD:
|
||||
// get HW diagnostics
|
||||
Serial.println("get HW diagnostics.");
|
||||
resp_size = set_response(&response, (const message *)data_dec, ret_valFFDD, sizeof(ret_valFFDD));
|
||||
break;
|
||||
case 0xFFDE:
|
||||
// get power status
|
||||
Serial.println("get power status.");
|
||||
resp_size = set_response(&response, (const message *)&data_dec, ret_valFFDE, sizeof(ret_valFFDE));
|
||||
break;
|
||||
case 0xFFDF:
|
||||
// set power type
|
||||
Serial.printf("set power type: %i\n", data_dec[4]);
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
case 0xFFEE:
|
||||
Serial.println("factory reset.");
|
||||
resp_size = set_response(&response, (const message *)data_dec);
|
||||
break;
|
||||
default:
|
||||
return "ERR";
|
||||
Serial.println(F("*********************************** unknown message"));
|
||||
}
|
||||
if (resp_size) {
|
||||
pChar->setValue((uint8_t *)&response, resp_size);
|
||||
pChar->notify();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void print_hex(uint8_t *value, uint8_t len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
Serial.print("0x");
|
||||
Serial.print(value[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
|
||||
class MyServerCallbacks: public BLEServerCallbacks {
|
||||
void onConnect(BLEServer* pServer) {
|
||||
Serial.print("connect ID: ");
|
||||
Serial.println(pServer->getConnId());
|
||||
/*pServer->updatePeerMTU(pServer->getConnId(), 310);
|
||||
Serial.print("MTU: ");
|
||||
Serial.println(pServer->getPeerMTU(pServer->getConnId()));*/
|
||||
class MyServerCallbacks : public BLEServerCallbacks {
|
||||
void onConnect(BLEServer *pServer) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
Serial.printf("connect ID: %i\n", pServer->getConnId());
|
||||
deviceConnected = true;
|
||||
BLEDevice::startAdvertising();
|
||||
};
|
||||
|
||||
void onDisconnect(BLEServer* pServer) {
|
||||
Serial.println("disconnect.");
|
||||
void onDisconnect(BLEServer *pServer) {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
Serial.printf("disconnect ID: %i\n\n", pServer->getConnId());
|
||||
deviceConnected = false;
|
||||
}
|
||||
|
||||
void onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
|
||||
Serialprintln("MTU changed: %d", pServer->getPeerMTU(pServer->getConnId()));
|
||||
void onMtuChanged(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) {
|
||||
Serial.printf("MTU changed: %d\n", pServer->getPeerMTU(pServer->getConnId()));
|
||||
}
|
||||
};
|
||||
|
||||
class coverCallbacks: public BLECharacteristicCallbacks {
|
||||
class coverCallbacks : public BLECharacteristicCallbacks {
|
||||
void onWrite(BLECharacteristic *pCharacteristic) {
|
||||
uint8_t *value = pCharacteristic->getData();
|
||||
|
||||
Serialprintln("Cover write %s:", pCharacteristic->toString().c_str());
|
||||
print_hex(value, pCharacteristic->getLength());
|
||||
|
||||
struct header data;
|
||||
memcpy((void *) &data, value, 4);
|
||||
Serialprintln("SRV: %x, CMD %x, SEQ %x, LEN %x", data.serviceID, data.cmdID, data.sequence, data.data_len);
|
||||
|
||||
switch ((data.serviceID << 8) | data.cmdID) {
|
||||
case 0xF701:
|
||||
// set position
|
||||
struct position pos;
|
||||
memcpy((void *) &pos, &value[4], data.data_len);
|
||||
Serialprintln("\tset position\tpos1 %f%%, pos2 %d, pos3 %d, tilt %d, velocity %d", pos.pos1/100.0, pos.pos2, pos.pos3, pos.tilt, pos.velocity);
|
||||
break;
|
||||
case 0xF7B8:
|
||||
// stop movement
|
||||
Serial.println("\tstop");
|
||||
break;
|
||||
case 0xF7BA:
|
||||
// activate scene
|
||||
Serialprintln("\tactivate scene\tscene #%d", (uint16_t) value[4]);
|
||||
break;
|
||||
case 0xFA5B:
|
||||
// get scene
|
||||
Serialprintln("\tget scene\tscene #%d", (uint16_t) value[4]);
|
||||
break;
|
||||
case 0xFAEA:
|
||||
// Reset Scene Automations
|
||||
// FIXME! wrong return value!
|
||||
Serialprintln("\treset scene automations\t");
|
||||
uint8_t ret[]={0xFA, 0xEA, data.sequence, 0x1, 0x0};
|
||||
Serial.print("ret: ");
|
||||
print_hex(ret, 5);
|
||||
pCharacteristic->setValue(ret, 5);
|
||||
pCharacteristic->indicate();
|
||||
break;
|
||||
}
|
||||
Serial.println();
|
||||
Serial.printf("Cover write %s\n", pCharacteristic->toString().c_str());
|
||||
decode(pCharacteristic);
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *pCharacteristic) {
|
||||
Serialprintln("Cover read: %s", pCharacteristic->toString().c_str());
|
||||
Serial.println();
|
||||
Serial.printf("Cover read: %s\n", pCharacteristic->toString().c_str());
|
||||
}
|
||||
|
||||
void onNotify(BLECharacteristic *pCharacteristic) {
|
||||
Serial.printf("Cover onNotify() %s\n", pCharacteristic->toString().c_str());
|
||||
}
|
||||
|
||||
void onStatus(BLECharacteristic *pCharacteristic, Status s, uint32_t code) {
|
||||
Serial.printf("Cover onStatus() %s: %s\n", BLEstate[s], pCharacteristic->toString().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
class batteryCallbacks: public BLECharacteristicCallbacks {
|
||||
class batteryCallbacks : public BLECharacteristicCallbacks {
|
||||
|
||||
void onWrite(BLECharacteristic *pCharacteristic) {
|
||||
uint8_t *value = pCharacteristic->getData();
|
||||
|
||||
Serialprintln("Battery write: %s:", pCharacteristic->toString().c_str());
|
||||
Serial.printf("Battery write: %s:", pCharacteristic->toString().c_str());
|
||||
print_hex(value, pCharacteristic->getLength());
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *pCharacteristic) {
|
||||
Serialprintln("Battery read: %s", pCharacteristic->toString().c_str());
|
||||
Serial.println();
|
||||
Serial.printf("Battery read: %s\n", pCharacteristic->toString().c_str());
|
||||
}
|
||||
|
||||
void onNotify(BLECharacteristic *pCharacteristic) {
|
||||
Serial.println("Battery onNotify()");
|
||||
}
|
||||
void onStatus(BLECharacteristic *pCharacteristic, Status s, uint32_t code) {
|
||||
Serial.println("Battery onStatus()");
|
||||
}
|
||||
};
|
||||
|
||||
class genericCallbacks: public BLECharacteristicCallbacks {
|
||||
class genericCallbacks : public BLECharacteristicCallbacks {
|
||||
|
||||
void onWrite(BLECharacteristic *pCharacteristic) {
|
||||
uint8_t *value = pCharacteristic->getData();
|
||||
//uint8_t *value = pCharacteristic->getData();
|
||||
|
||||
Serialprintln("generic write %s:", pCharacteristic->toString().c_str());
|
||||
print_hex(value, pCharacteristic->getLength());
|
||||
Serial.println();
|
||||
Serial.printf("generic write %s:\n", pCharacteristic->toString().c_str());
|
||||
//print_hex(value, pCharacteristic->getLength());
|
||||
}
|
||||
|
||||
void onRead(BLECharacteristic *pCharacteristic) {
|
||||
Serialprintln("generic read %s.", pCharacteristic->toString().c_str());
|
||||
Serial.println();
|
||||
Serial.printf("generic read %s.\n", pCharacteristic->toString().c_str());
|
||||
}
|
||||
void onNotify(BLECharacteristic *pCharacteristic) {
|
||||
Serial.printf("generic onNotify() %s\n", pCharacteristic->toString().c_str());
|
||||
} // not used
|
||||
void onStatus(BLECharacteristic *pCharacteristic, Status s, uint32_t code) {
|
||||
Serial.printf("generic onStatus() %s - %s\n", BLEstate[s], pCharacteristic->toString().c_str());
|
||||
}; // not used
|
||||
};
|
||||
|
||||
void setup() {
|
||||
@@ -197,8 +344,6 @@ void setup() {
|
||||
Serial.println(NAME " initializing ...");
|
||||
|
||||
BLEDevice::init(NAME);
|
||||
Serialprintln("MTU: %d", BLEDevice::getMTU());
|
||||
|
||||
// Create the BLE Server
|
||||
pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new MyServerCallbacks());
|
||||
@@ -208,59 +353,63 @@ void setup() {
|
||||
// Create a BLE Characteristic
|
||||
pCharacteristic_cover = pCovService->createCharacteristic(
|
||||
COVER_CHAR_UUID,
|
||||
BLECharacteristic::PROPERTY_NOTIFY |
|
||||
BLECharacteristic::PROPERTY_WRITE |
|
||||
BLECharacteristic::PROPERTY_WRITE_NR
|
||||
);
|
||||
BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
|
||||
pCharacteristic_cover->setCallbacks(new coverCallbacks());
|
||||
// Create a BLE Descriptor
|
||||
BLEDescriptor *pDesc1 = new BLEDescriptor("2901", 10);
|
||||
pDesc1->setValue("cover");
|
||||
/* BLEDescriptor *pDesc1 = new BLEDescriptor("2901", 10);
|
||||
pDesc1->setValue("cover");*/
|
||||
//pCharacteristic_cover->addDescriptor(pDesc1);
|
||||
pCharacteristic_cover->addDescriptor(new BLE2902());
|
||||
pCharacteristic_cover->addDescriptor(pDesc1);
|
||||
|
||||
|
||||
pCharacteristic_unknown = pCovService->createCharacteristic(
|
||||
XXX_CHAR_UUID,
|
||||
BLECharacteristic::PROPERTY_INDICATE |
|
||||
BLECharacteristic::PROPERTY_WRITE |
|
||||
BLECharacteristic::PROPERTY_WRITE_NR
|
||||
);
|
||||
BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
|
||||
pCharacteristic_unknown->setCallbacks(new genericCallbacks());
|
||||
|
||||
pCharacteristic_unknown->addDescriptor(new BLE2902());
|
||||
|
||||
BLEService *pBatService = pServer->createService(BAT_SERVICE_UUID);
|
||||
|
||||
pCharacteristic_bat = pBatService->createCharacteristic(
|
||||
BAT_CHAR_UUID,
|
||||
BLECharacteristic::PROPERTY_READ
|
||||
);
|
||||
BLECharacteristic::PROPERTY_READ);
|
||||
pCharacteristic_bat->setCallbacks(new batteryCallbacks());
|
||||
pBatService->addCharacteristic(pCharacteristic_bat);
|
||||
uint8_t battery_level = 42;
|
||||
pCharacteristic_bat->setValue(&battery_level, 1);
|
||||
pCharacteristic_bat->addDescriptor(new BLE2902());
|
||||
|
||||
// BLEService *pFWService = pServer->createService(FW_SERVICE_UUID);
|
||||
// pCharacteristic_fw = pCovService->createCharacteristic(
|
||||
// CHAR_FW_UUID,
|
||||
// BLECharacteristic::PROPERTY_READ |
|
||||
// BLECharacteristic::PROPERTY_WRITE |
|
||||
// BLECharacteristic::PROPERTY_WRITE_NR
|
||||
// );
|
||||
// pCharacteristic_fw->setCallbacks(new genericCallbacks());
|
||||
// pCharacteristic_fw->addDescriptor(new BLE2902());
|
||||
// pCharacteristic_fw->addDescriptor(pDesc2);
|
||||
//BLEDescriptor *pDesc2 = new BLEDescriptor("2901", 10);
|
||||
//pDesc2->setValue("firmware");
|
||||
BLEService *pFWService = pServer->createService(FW_SERVICE_UUID);
|
||||
pCharacteristic_fw = pFWService->createCharacteristic(
|
||||
FW_CHAR_UUID,
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
|
||||
pCharacteristic_fw->setCallbacks(new genericCallbacks());
|
||||
pCharacteristic_fw->addDescriptor(new BLE2902());
|
||||
BLEDescriptor *pDesc2 = new BLEDescriptor("2901", 10);
|
||||
pDesc2->setValue("firmware");
|
||||
pCharacteristic_fw->addDescriptor(pDesc2);
|
||||
|
||||
// Start the service
|
||||
|
||||
BLEService *pDEVService = pServer->createService(DEV_SERVICE_UUID);
|
||||
pCharacteristic_dev = pDEVService->createCharacteristic(
|
||||
SWC_CHAR_UUID,
|
||||
BLECharacteristic::PROPERTY_READ);
|
||||
pCharacteristic_dev->setValue(FW_VERSION);
|
||||
pCharacteristic_dev->setCallbacks(new genericCallbacks());
|
||||
pCharacteristic_ser = pDEVService->createCharacteristic(
|
||||
SER_CHAR_UUID,
|
||||
BLECharacteristic::PROPERTY_READ);
|
||||
pCharacteristic_ser->setValue(SERIAL_NR);
|
||||
pCharacteristic_ser->setCallbacks(new genericCallbacks());
|
||||
|
||||
// Start the services
|
||||
pCovService->start();
|
||||
//pFWService->start();
|
||||
pFWService->start();
|
||||
pBatService->start();
|
||||
pDEVService->start();
|
||||
|
||||
// Start advertising
|
||||
BLEAdvertisementData AdvertisementData;
|
||||
const String manufacturerData = String("\x19\x08\x00\x00\x2A\x00\x00\x00\x00\x00\xA2",11);
|
||||
// Hunter Douglas ^^--^^ ^^ ID-Type
|
||||
const String manufacturerData = String("\x19\x08\x00\x00\x2A\x00\x00\x00\x00\x00\xA2", 11);
|
||||
// Hunter Douglas ^^--^^ ^^key^^ ^^ ID-Type
|
||||
AdvertisementData.setManufacturerData(manufacturerData);
|
||||
AdvertisementData.setPartialServices(BLEUUID(COVER_SERVICE_UUID));
|
||||
AdvertisementData.setFlags((1 << 2) | (1 << 1)); // [BR/EDR Not Supported] | [LE General Discoverable Mode]
|
||||
@@ -268,7 +417,6 @@ void setup() {
|
||||
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||
|
||||
pAdvertising->setAdvertisementData(AdvertisementData);
|
||||
|
||||
BLEDevice::startAdvertising();
|
||||
|
||||
Serial.println("Device " NAME " ready.");
|
||||
|
||||
540
emu/PV_BLE_cover/user_settings.h
Normal file
540
emu/PV_BLE_cover/user_settings.h
Normal file
@@ -0,0 +1,540 @@
|
||||
/* user_settings_template.h
|
||||
*
|
||||
* Copyright (C) 2006-2024 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfSSL.
|
||||
*
|
||||
* wolfSSL is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfSSL is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
*/
|
||||
|
||||
/* Example wolfSSL user settings with #if 0/1 gates to enable/disable algorithms and features.
|
||||
* This file is included with wolfssl/wolfcrypt/settings.h when WOLFSSL_USER_SETTINGS is defined.
|
||||
* Based on IDE/GCC-ARM/Headers/user_settings.h
|
||||
*/
|
||||
|
||||
#ifndef WOLFSSL_USER_SETTINGS_H
|
||||
#define WOLFSSL_USER_SETTINGS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* If TARGET_EMBEDDED is defined then small target settings are used */
|
||||
#if !(defined(__MACH__) || defined(__FreeBSD__) || defined(__linux__) || defined(_WIN32))
|
||||
#define TARGET_EMBEDDED
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Platform */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
#define WOLFSSL_GENERAL_ALIGNMENT 4
|
||||
#define SIZEOF_LONG_LONG 8
|
||||
#if 0
|
||||
#define NO_64BIT /* disable use of 64-bit variables */
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_EMBEDDED
|
||||
/* disable mutex locking */
|
||||
#define SINGLE_THREADED
|
||||
|
||||
/* reduce stack use. For variables over 100 bytes allocate from heap */
|
||||
#define WOLFSSL_SMALL_STACK
|
||||
|
||||
/* Disable the built-in socket support and use the IO callbacks.
|
||||
* Set IO callbacks with wolfSSL_CTX_SetIORecv/wolfSSL_CTX_SetIOSend
|
||||
*/
|
||||
#define WOLFSSL_USER_IO
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Math Configuration */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Wolf Single Precision Math */
|
||||
#if 1
|
||||
#define WOLFSSL_HAVE_SP_RSA
|
||||
#define WOLFSSL_HAVE_SP_DH
|
||||
#define WOLFSSL_HAVE_SP_ECC
|
||||
//#define WOLFSSL_SP_4096 /* Enable RSA/RH 4096-bit support */
|
||||
//#define WOLFSSL_SP_384 /* Enable ECC 384-bit SECP384R1 support */
|
||||
|
||||
//#define WOLFSSL_SP_MATH /* only SP math - disables integer.c/tfm.c */
|
||||
#define WOLFSSL_SP_MATH_ALL /* use SP math for all key sizes and curves */
|
||||
|
||||
//#define WOLFSSL_SP_NO_MALLOC
|
||||
//#define WOLFSSL_SP_DIV_32 /* do not use 64-bit divides */
|
||||
|
||||
#ifdef TARGET_EMBEDDED
|
||||
/* use smaller version of code */
|
||||
#define WOLFSSL_SP_SMALL
|
||||
#else
|
||||
/* SP Assembly Speedups - specific to chip type */
|
||||
#define WOLFSSL_SP_ASM
|
||||
#endif
|
||||
//#define WOLFSSL_SP_X86_64
|
||||
//#define WOLFSSL_SP_X86
|
||||
//#define WOLFSSL_SP_ARM32_ASM
|
||||
//#define WOLFSSL_SP_ARM64_ASM
|
||||
//#define WOLFSSL_SP_ARM_THUMB_ASM
|
||||
//#define WOLFSSL_SP_ARM_CORTEX_M_ASM
|
||||
#elif 1
|
||||
/* Fast Math (tfm.c) (stack based and timing resistant) */
|
||||
#define USE_FAST_MATH
|
||||
#define TFM_TIMING_RESISTANT
|
||||
#else
|
||||
/* Normal (integer.c) (heap based, not timing resistant) - not recommended*/
|
||||
#define USE_INTEGER_HEAP_MATH
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Crypto */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* RSA */
|
||||
#undef NO_RSA
|
||||
#if 0
|
||||
#ifdef USE_FAST_MATH
|
||||
/* Maximum math bits (Max RSA key bits * 2) */
|
||||
#define FP_MAX_BITS 4096
|
||||
#endif
|
||||
|
||||
/* half as much memory but twice as slow */
|
||||
//#define RSA_LOW_MEM
|
||||
|
||||
/* Enables blinding mode, to prevent timing attacks */
|
||||
#define WC_RSA_BLINDING
|
||||
|
||||
/* RSA PSS Support */
|
||||
#define WC_RSA_PSS
|
||||
#else
|
||||
#define NO_RSA
|
||||
#endif
|
||||
|
||||
/* DH */
|
||||
#undef NO_DH
|
||||
#if 0
|
||||
/* Use table for DH instead of -lm (math) lib dependency */
|
||||
#if 1
|
||||
#define WOLFSSL_DH_CONST
|
||||
#define HAVE_FFDHE_2048
|
||||
//#define HAVE_FFDHE_4096
|
||||
//#define HAVE_FFDHE_6144
|
||||
//#define HAVE_FFDHE_8192
|
||||
#endif
|
||||
#else
|
||||
#define NO_DH
|
||||
#endif
|
||||
|
||||
/* ECC */
|
||||
#undef HAVE_ECC
|
||||
#if 0
|
||||
#define HAVE_ECC
|
||||
|
||||
/* Manually define enabled curves */
|
||||
#define ECC_USER_CURVES
|
||||
|
||||
#ifdef ECC_USER_CURVES
|
||||
/* Manual Curve Selection */
|
||||
//#define HAVE_ECC192
|
||||
//#define HAVE_ECC224
|
||||
#undef NO_ECC256
|
||||
//#define HAVE_ECC384
|
||||
//#define HAVE_ECC521
|
||||
#endif
|
||||
|
||||
/* Fixed point cache (speeds repeated operations against same private key) */
|
||||
//#define FP_ECC
|
||||
#ifdef FP_ECC
|
||||
/* Bits / Entries */
|
||||
#define FP_ENTRIES 2
|
||||
#define FP_LUT 4
|
||||
#endif
|
||||
|
||||
/* Optional ECC calculation method */
|
||||
/* Note: doubles heap usage, but slightly faster */
|
||||
#define ECC_SHAMIR
|
||||
|
||||
/* Reduces heap usage, but slower */
|
||||
#define ECC_TIMING_RESISTANT
|
||||
|
||||
/* Compressed ECC Key Support */
|
||||
//#define HAVE_COMP_KEY
|
||||
|
||||
/* Use alternate ECC size for ECC math */
|
||||
#ifdef USE_FAST_MATH
|
||||
/* MAX ECC BITS = ROUND8(MAX ECC) * 2 */
|
||||
#if defined(NO_RSA) && defined(NO_DH)
|
||||
/* Custom fastmath size if not using RSA/DH */
|
||||
#define FP_MAX_BITS (256 * 2)
|
||||
#else
|
||||
/* use heap allocation for ECC points */
|
||||
#define ALT_ECC_SIZE
|
||||
|
||||
/* wolfSSL will compute the FP_MAX_BITS_ECC, but it can be overridden */
|
||||
//#define FP_MAX_BITS_ECC (256 * 2)
|
||||
#endif
|
||||
|
||||
/* Speedups specific to curve */
|
||||
#ifndef NO_ECC256
|
||||
#define TFM_ECC256
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* AES */
|
||||
#undef NO_AES
|
||||
#if 1
|
||||
#define HAVE_AES_CBC
|
||||
|
||||
/* GCM Method: GCM_TABLE_4BIT, GCM_SMALL, GCM_WORD32 or GCM_TABLE */
|
||||
#define HAVE_AESGCM
|
||||
#ifdef TARGET_EMBEDDED
|
||||
#define GCM_SMALL
|
||||
#else
|
||||
#define GCM_TABLE_4BIT
|
||||
#endif
|
||||
|
||||
#define WOLFSSL_AES_DIRECT
|
||||
//#define HAVE_AES_ECB
|
||||
#define WOLFSSL_AES_COUNTER
|
||||
//#define HAVE_AESCCM
|
||||
#else
|
||||
#define NO_AES
|
||||
#endif
|
||||
|
||||
|
||||
/* DES3 */
|
||||
#undef NO_DES3
|
||||
#if 0
|
||||
#else
|
||||
#define NO_DES3
|
||||
#endif
|
||||
|
||||
/* ChaCha20 / Poly1305 */
|
||||
#undef HAVE_CHACHA
|
||||
#undef HAVE_POLY1305
|
||||
#if 0
|
||||
#define HAVE_CHACHA
|
||||
#define HAVE_POLY1305
|
||||
|
||||
/* Needed for Poly1305 */
|
||||
#define HAVE_ONE_TIME_AUTH
|
||||
#endif
|
||||
|
||||
/* Ed25519 / Curve25519 */
|
||||
#undef HAVE_CURVE25519
|
||||
#undef HAVE_ED25519
|
||||
#if 0
|
||||
#define HAVE_CURVE25519
|
||||
#define HAVE_ED25519 /* ED25519 Requires SHA512 */
|
||||
|
||||
/* Optionally use small math (less flash usage, but much slower) */
|
||||
#if 1
|
||||
#define CURVED25519_SMALL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Hashing */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Sha */
|
||||
#undef NO_SHA
|
||||
#if 1
|
||||
/* 1k smaller, but 25% slower */
|
||||
//#define USE_SLOW_SHA
|
||||
#else
|
||||
#define NO_SHA
|
||||
#endif
|
||||
|
||||
/* Sha256 */
|
||||
#undef NO_SHA256
|
||||
#if 1
|
||||
/* not unrolled - ~2k smaller and ~25% slower */
|
||||
//#define USE_SLOW_SHA256
|
||||
|
||||
/* Sha224 */
|
||||
#if 0
|
||||
#define WOLFSSL_SHA224
|
||||
#endif
|
||||
#else
|
||||
#define NO_SHA256
|
||||
#endif
|
||||
|
||||
/* Sha512 */
|
||||
#undef WOLFSSL_SHA512
|
||||
#if 0
|
||||
#define WOLFSSL_SHA512
|
||||
|
||||
/* Sha384 */
|
||||
#undef WOLFSSL_SHA384
|
||||
#if 0
|
||||
#define WOLFSSL_SHA384
|
||||
#endif
|
||||
|
||||
/* over twice as small, but 50% slower */
|
||||
//#define USE_SLOW_SHA512
|
||||
#endif
|
||||
|
||||
/* Sha3 */
|
||||
#undef WOLFSSL_SHA3
|
||||
#if 0
|
||||
#define WOLFSSL_SHA3
|
||||
#endif
|
||||
|
||||
/* MD5 */
|
||||
#undef NO_MD5
|
||||
#if 0
|
||||
|
||||
#else
|
||||
#define NO_MD5
|
||||
#endif
|
||||
|
||||
/* HKDF */
|
||||
#undef HAVE_HKDF
|
||||
#if 0
|
||||
#define HAVE_HKDF
|
||||
#endif
|
||||
|
||||
/* CMAC */
|
||||
#undef WOLFSSL_CMAC
|
||||
#if 0
|
||||
#define WOLFSSL_CMAC
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Benchmark / Test */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
#ifdef TARGET_EMBEDDED
|
||||
/* Use reduced benchmark / test sizes */
|
||||
#define BENCH_EMBEDDED
|
||||
#endif
|
||||
|
||||
/* Use test buffers from array (not filesystem) */
|
||||
#ifndef NO_FILESYSTEM
|
||||
#define USE_CERT_BUFFERS_256
|
||||
#define USE_CERT_BUFFERS_2048
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Debugging */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#undef DEBUG_WOLFSSL
|
||||
#undef NO_ERROR_STRINGS
|
||||
#if 0
|
||||
#define DEBUG_WOLFSSL
|
||||
#else
|
||||
#if 0
|
||||
#define NO_ERROR_STRINGS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Memory */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Override Memory API's */
|
||||
#if 0
|
||||
#define XMALLOC_OVERRIDE
|
||||
|
||||
/* prototypes for user heap override functions */
|
||||
/* Note: Realloc only required for normal math */
|
||||
/* Note2: XFREE(NULL) must be properly handled */
|
||||
#include <stddef.h> /* for size_t */
|
||||
extern void *myMalloc(size_t n, void* heap, int type);
|
||||
extern void myFree(void *p, void* heap, int type);
|
||||
extern void *myRealloc(void *p, size_t n, void* heap, int type);
|
||||
|
||||
#define XMALLOC(n, h, t) myMalloc(n, h, t)
|
||||
#define XFREE(p, h, t) myFree(p, h, t)
|
||||
#define XREALLOC(p, n, h, t) myRealloc(p, n, h, t)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
/* Static memory requires fast math */
|
||||
#define WOLFSSL_STATIC_MEMORY
|
||||
|
||||
/* Disable fallback malloc/free */
|
||||
#define WOLFSSL_NO_MALLOC
|
||||
#if 1
|
||||
#define WOLFSSL_MALLOC_CHECK /* trap malloc failure */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Memory callbacks */
|
||||
#if 0
|
||||
#undef USE_WOLFSSL_MEMORY
|
||||
#define USE_WOLFSSL_MEMORY
|
||||
|
||||
/* Use this to measure / print heap usage */
|
||||
#if 0
|
||||
#define WOLFSSL_TRACK_MEMORY
|
||||
#define WOLFSSL_DEBUG_MEMORY
|
||||
#endif
|
||||
#else
|
||||
#ifndef WOLFSSL_STATIC_MEMORY
|
||||
#define NO_WOLFSSL_MEMORY
|
||||
/* Otherwise we will use stdlib malloc, free and realloc */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Port */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Override Current Time */
|
||||
#if 0
|
||||
/* Allows custom "custom_time()" function to be used for benchmark */
|
||||
#define WOLFSSL_USER_CURRTIME
|
||||
#define WOLFSSL_GMTIME
|
||||
#define USER_TICKS
|
||||
extern unsigned long my_time(unsigned long* timer);
|
||||
#define XTIME my_time
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* RNG */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Choose RNG method */
|
||||
#if 0
|
||||
/* Custom Seed Source */
|
||||
#if 0
|
||||
/* Size of returned HW RNG value */
|
||||
#define CUSTOM_RAND_TYPE unsigned int
|
||||
extern unsigned int my_rng_seed_gen(void);
|
||||
#undef CUSTOM_RAND_GENERATE
|
||||
#define CUSTOM_RAND_GENERATE my_rng_seed_gen
|
||||
#endif
|
||||
|
||||
/* Use built-in P-RNG (SHA256 based) with HW RNG */
|
||||
/* P-RNG + HW RNG (P-RNG is ~8K) */
|
||||
#undef HAVE_HASHDRBG
|
||||
#define HAVE_HASHDRBG
|
||||
#else
|
||||
#undef WC_NO_HASHDRBG
|
||||
#define WC_NO_HASHDRBG
|
||||
|
||||
/* Bypass P-RNG and use only HW RNG */
|
||||
extern int my_rng_gen_block(unsigned char* output, unsigned int sz);
|
||||
#undef CUSTOM_RAND_GENERATE_BLOCK
|
||||
#define CUSTOM_RAND_GENERATE_BLOCK my_rng_gen_block
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Custom Standard Lib */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Allows override of all standard library functions */
|
||||
#undef STRING_USER
|
||||
#if 0
|
||||
#define STRING_USER
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define USE_WOLF_STRSEP
|
||||
#define XSTRSEP(s1,d) wc_strsep((s1),(d))
|
||||
|
||||
#define USE_WOLF_STRTOK
|
||||
#define XSTRTOK(s1,d,ptr) wc_strtok((s1),(d),(ptr))
|
||||
|
||||
#define XSTRNSTR(s1,s2,n) mystrnstr((s1),(s2),(n))
|
||||
|
||||
#define XMEMCPY(d,s,l) memcpy((d),(s),(l))
|
||||
#define XMEMSET(b,c,l) memset((b),(c),(l))
|
||||
#define XMEMCMP(s1,s2,n) memcmp((s1),(s2),(n))
|
||||
#define XMEMMOVE(d,s,l) memmove((d),(s),(l))
|
||||
|
||||
#define XSTRLEN(s1) strlen((s1))
|
||||
#define XSTRNCPY(s1,s2,n) strncpy((s1),(s2),(n))
|
||||
#define XSTRSTR(s1,s2) strstr((s1),(s2))
|
||||
|
||||
#define XSTRNCMP(s1,s2,n) strncmp((s1),(s2),(n))
|
||||
#define XSTRNCAT(s1,s2,n) strncat((s1),(s2),(n))
|
||||
#define XSTRNCASECMP(s1,s2,n) strncasecmp((s1),(s2),(n))
|
||||
|
||||
#define XSNPRINTF snprintf
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Enable Features */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#define WOLFSSL_TLS13
|
||||
#define WOLFSSL_OLD_PRIME_CHECK /* Use faster DH prime checking */
|
||||
#define HAVE_TLS_EXTENSIONS
|
||||
#define HAVE_SUPPORTED_CURVES
|
||||
#define WOLFSSL_BASE64_ENCODE
|
||||
|
||||
//#define WOLFSSL_KEY_GEN /* For RSA Key gen only */
|
||||
//#define KEEP_PEER_CERT
|
||||
//#define HAVE_COMP_KEY
|
||||
|
||||
/* TLS Session Cache */
|
||||
#if 0
|
||||
#define SMALL_SESSION_CACHE
|
||||
#else
|
||||
#define NO_SESSION_CACHE
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Disable Features */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
#define NO_WOLFSSL_SERVER
|
||||
#define NO_WOLFSSL_CLIENT
|
||||
//#define NO_CRYPT_TEST
|
||||
//#define NO_CRYPT_BENCHMARK
|
||||
#define WOLFCRYPT_ONLY
|
||||
|
||||
/* do not warm when file is included to be built and not required to be */
|
||||
#define WOLFSSL_IGNORE_FILE_WARN
|
||||
|
||||
/* In-lining of misc.c functions */
|
||||
/* If defined, must include wolfcrypt/src/misc.c in build */
|
||||
/* Slower, but about 1k smaller */
|
||||
//#define NO_INLINE
|
||||
|
||||
#ifdef TARGET_EMBEDDED
|
||||
#define NO_FILESYSTEM
|
||||
#define NO_WRITEV
|
||||
#define NO_MAIN_DRIVER
|
||||
#define NO_DEV_RANDOM
|
||||
#endif
|
||||
|
||||
#define NO_OLD_TLS
|
||||
#define NO_PSK
|
||||
|
||||
#define NO_DSA
|
||||
#define NO_RC4
|
||||
#define NO_MD4
|
||||
#define NO_PWDBASED
|
||||
//#define NO_CODING
|
||||
//#define NO_ASN_TIME
|
||||
//#define NO_CERTS
|
||||
//#define NO_SIG_WRAPPER
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WOLFSSL_USER_SETTINGS_H */
|
||||
154
pyproject.toml
154
pyproject.toml
@@ -1,12 +1,11 @@
|
||||
# pyproject.toml
|
||||
|
||||
#[tool.setuptools.packages.find]
|
||||
#where = ["custom_components/"]
|
||||
#include = ["bms_ble"]
|
||||
[project]
|
||||
requires-python = ">=3.12.0"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "8.0"
|
||||
addopts="--cov=custom_components.bms_ble --cov-report=term-missing --cov-fail-under=100"
|
||||
addopts="--cov=custom_components.hunterdouglas_powerview_ble --cov-report=term-missing --cov-fail-under=100"
|
||||
pythonpath = [
|
||||
"custom_components.hunterdouglas_powerview_ble",
|
||||
]
|
||||
@@ -15,5 +14,152 @@ testpaths = [
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
|
||||
# ruff configuration taken from HA 2024.11.2 (less ignores)
|
||||
[tool.ruff]
|
||||
required-version = ">=0.6.8"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"scripts/*" = ["T201"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"A001", # Variable {name} is shadowing a Python builtin
|
||||
"ASYNC210", # Async functions should not call blocking HTTP methods
|
||||
"ASYNC220", # Async functions should not create subprocesses with blocking methods
|
||||
"ASYNC221", # Async functions should not run processes with blocking methods
|
||||
"ASYNC222", # Async functions should not wait on processes with blocking methods
|
||||
"ASYNC230", # Async functions should not open files with blocking methods like open
|
||||
"ASYNC251", # Async functions should not call time.sleep
|
||||
"B002", # Python does not support the unary prefix increment
|
||||
"B005", # Using .strip() with multi-character strings is misleading
|
||||
"B007", # Loop control variable {name} not used within loop body
|
||||
"B014", # Exception handler with duplicate exception
|
||||
"B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it.
|
||||
"B017", # pytest.raises(BaseException) should be considered evil
|
||||
"B018", # Found useless attribute access. Either assign it to a variable or remove it.
|
||||
"B023", # Function definition does not bind loop variable {name}
|
||||
"B026", # Star-arg unpacking after a keyword argument is strongly discouraged
|
||||
"B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)?
|
||||
"B904", # Use raise from to specify exception cause
|
||||
"B905", # zip() without an explicit strict= parameter
|
||||
"BLE",
|
||||
"C", # complexity
|
||||
"COM818", # Trailing comma on bare tuple prohibited
|
||||
"D", # docstrings
|
||||
"DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow()
|
||||
"DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts)
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes/autoflake
|
||||
"F541", # f-string without any placeholders
|
||||
"FLY", # flynt
|
||||
"FURB", # refurb
|
||||
"G", # flake8-logging-format
|
||||
"I", # isort
|
||||
"INP", # flake8-no-pep420
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"ICN001", # import concentions; {name} should be imported as {asname}
|
||||
"LOG", # flake8-logging
|
||||
"N804", # First argument of a class method should be named cls
|
||||
"N805", # First argument of a method should be named self
|
||||
"N815", # Variable {name} in class scope should not be mixedCase
|
||||
"PERF", # Perflint
|
||||
"PGH", # pygrep-hooks
|
||||
"PIE", # flake8-pie
|
||||
"PL", # pylint
|
||||
"PT", # flake8-pytest-style
|
||||
"PTH", # flake8-pathlib
|
||||
"PYI", # flake8-pyi
|
||||
"RET", # flake8-return
|
||||
"RSE", # flake8-raise
|
||||
"RUF005", # Consider iterable unpacking instead of concatenation
|
||||
"RUF006", # Store a reference to the return value of asyncio.create_task
|
||||
"RUF010", # Use explicit conversion flag
|
||||
"RUF013", # PEP 484 prohibits implicit Optional
|
||||
"RUF017", # Avoid quadratic list summation
|
||||
"RUF018", # Avoid assignment expressions in assert statements
|
||||
"RUF019", # Unnecessary key check before dictionary access
|
||||
# "RUF100", # Unused `noqa` directive; temporarily every now and then to clean them up
|
||||
"S102", # Use of exec detected
|
||||
"S103", # bad-file-permissions
|
||||
"S108", # hardcoded-temp-file
|
||||
"S306", # suspicious-mktemp-usage
|
||||
"S307", # suspicious-eval-usage
|
||||
"S313", # suspicious-xmlc-element-tree-usage
|
||||
"S314", # suspicious-xml-element-tree-usage
|
||||
"S315", # suspicious-xml-expat-reader-usage
|
||||
"S316", # suspicious-xml-expat-builder-usage
|
||||
"S317", # suspicious-xml-sax-usage
|
||||
"S318", # suspicious-xml-mini-dom-usage
|
||||
"S319", # suspicious-xml-pull-dom-usage
|
||||
"S320", # suspicious-xmle-tree-usage
|
||||
"S601", # paramiko-call
|
||||
"S602", # subprocess-popen-with-shell-equals-true
|
||||
"S604", # call-with-shell-equals-true
|
||||
"S608", # hardcoded-sql-expression
|
||||
"S609", # unix-command-wildcard-injection
|
||||
"SIM", # flake8-simplify
|
||||
"SLF", # flake8-self
|
||||
"SLOT", # flake8-slots
|
||||
"T100", # Trace found: {name} used
|
||||
"T20", # flake8-print
|
||||
"TCH", # flake8-type-checking
|
||||
"TID", # Tidy imports
|
||||
"TRY", # tryceratops
|
||||
"UP", # pyupgrade
|
||||
"UP031", # Use format specifiers instead of percent format
|
||||
"UP032", # Use f-string instead of `format` call
|
||||
"W", # pycodestyle
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"D202", # No blank lines allowed after function docstring
|
||||
"D203", # 1 blank line required before class docstring
|
||||
"D213", # Multi-line docstring summary should start at the second line
|
||||
"D406", # Section name should end with a newline
|
||||
"D407", # Section name underlining
|
||||
"E501", # line too long
|
||||
|
||||
# "PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives
|
||||
# "PLR0911", # Too many return statements ({returns} > {max_returns})
|
||||
# "PLR0912", # Too many branches ({branches} > {max_branches})
|
||||
# "PLR0913", # Too many arguments to function call ({c_args} > {max_args})
|
||||
# "PLR0915", # Too many statements ({statements} > {max_statements})
|
||||
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
|
||||
# "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
|
||||
# "PT004", # Fixture {fixture} does not return anything, add leading underscore
|
||||
# "PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception
|
||||
# "PT018", # Assertion should be broken down into multiple parts
|
||||
# "RUF001", # String contains ambiguous unicode character.
|
||||
# "RUF002", # Docstring contains ambiguous unicode character.
|
||||
# "RUF003", # Comment contains ambiguous unicode character.
|
||||
# "RUF015", # Prefer next(...) over single element slice
|
||||
# "SIM102", # Use a single if statement instead of nested if statements
|
||||
# "SIM103", # Return the condition {condition} directly
|
||||
# "SIM108", # Use ternary operator {contents} instead of if-else-block
|
||||
# "SIM115", # Use context handler for opening files
|
||||
|
||||
# Moving imports into type-checking blocks can mess with pytest.patch()
|
||||
"TCH001", # Move application import {} into a type-checking block
|
||||
"TCH002", # Move third-party import {} into a type-checking block
|
||||
"TCH003", # Move standard library import {} into a type-checking block
|
||||
|
||||
"TRY003", # Avoid specifying long messages outside the exception class
|
||||
"TRY400", # Use `logging.exception` instead of `logging.error`
|
||||
# Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923
|
||||
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
|
||||
|
||||
# May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
||||
"W191",
|
||||
"E111",
|
||||
"E114",
|
||||
"E117",
|
||||
"D206",
|
||||
"D300",
|
||||
"Q",
|
||||
"COM812",
|
||||
"COM819",
|
||||
"ISC001",
|
||||
|
||||
# Disabled because ruff does not understand type of __all__ generated by a function
|
||||
"PLE0605"
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
homeassistant==2024.8.0
|
||||
homeassistant==2024.11.0
|
||||
pip>=21.3.1
|
||||
ruff==0.4.2
|
||||
ruff==0.6.8
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
pip>=21.3.1
|
||||
homeassistant==2024.6.0
|
||||
-r requirements.txt
|
||||
|
||||
wheel
|
||||
home-assistant-bluetooth
|
||||
habluetooth>=2.4.0
|
||||
habluetooth>=3.6.0
|
||||
bluetooth-adapters
|
||||
pytest>=8.0.2
|
||||
pytest-cov>=4.0.0
|
||||
pytest-socket>=0.5.0
|
||||
pytest-asyncio
|
||||
pytest>=8.3.3
|
||||
pytest-cov>=5.0.0
|
||||
pytest-socket>=0.7.0
|
||||
pytest-asyncio>=0.24.0
|
||||
sqlalchemy
|
||||
freezegun
|
||||
requests-mock
|
||||
@@ -16,10 +16,10 @@ aiohttp
|
||||
aiohttp_cors
|
||||
aiohttp-fast-url-dispatcher
|
||||
aiohttp-zlib-ng
|
||||
bleak>=0.19.0
|
||||
bleak-retry-connector>=3.3.0
|
||||
bleak>=0.22.3
|
||||
bleak-retry-connector>=3.6.0
|
||||
bluetooth-data-tools
|
||||
pyserial-asyncio
|
||||
pyudev
|
||||
pytest-homeassistant-custom-component==0.13.132
|
||||
pytest-homeassistant-custom-component==0.13.181
|
||||
|
||||
|
||||
Reference in New Issue
Block a user