Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix support for BLE TRV #28

Merged
merged 22 commits into from
Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add binary sensor
  • Loading branch information
forabi committed Jun 7, 2023
commit dafcb5d5cfb246bfc48aecaf5ec5dcfd2cfde158
1 change: 1 addition & 0 deletions custom_components/tuya_ble/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Platform.CLIMATE,
Platform.NUMBER,
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.SELECT,
Platform.SWITCH,
]
Expand Down
164 changes: 164 additions & 0 deletions custom_components/tuya_ble/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""The Tuya BLE integration."""
from __future__ import annotations

from dataclasses import dataclass

import logging
from typing import Callable

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import (
DOMAIN,
)
from .devices import TuyaBLEData, TuyaBLEEntity, TuyaBLEProductInfo
from .tuya_ble import TuyaBLEDataPointType, TuyaBLEDevice

_LOGGER = logging.getLogger(__name__)

SIGNAL_STRENGTH_DP_ID = -1


TuyaBLEBinarySensorIsAvailable = (
Callable[["TuyaBLEBinarySensor", TuyaBLEProductInfo], bool] | None
)


@dataclass
class TuyaBLEBinarySensorMapping:
dp_id: int
description: BinarySensorEntityDescription
force_add: bool = True
dp_type: TuyaBLEDataPointType | None = None
getter: Callable[[TuyaBLEBinarySensor], None] | None = None
coefficient: float = 1.0
icons: list[str] | None = None
is_available: TuyaBLEBinarySensorIsAvailable = None


@dataclass
class TuyaBLECategoryBinarySensorMapping:
products: dict[str, list[TuyaBLEBinarySensorMapping]] | None = None
mapping: list[TuyaBLEBinarySensorMapping] | None = None


mapping: dict[str, TuyaBLECategoryBinarySensorMapping] = {
"wk": TuyaBLECategoryBinarySensorMapping(
products={
"drlajpqc": [ # Thermostatic Radiator Valve
TuyaBLEBinarySensorMapping(
dp_id=105,
description=BinarySensorEntityDescription(
key="low_battery",
icon="mdi:battery-alert",
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=True,
),
),
],
},
),
}


def get_mapping_by_device(device: TuyaBLEDevice) -> list[TuyaBLEBinarySensorMapping]:
category = mapping.get(device.category)
if category is not None and category.products is not None:
product_mapping = category.products.get(device.product_id)
if product_mapping is not None:
return product_mapping
if category.mapping is not None:
return category.mapping
else:
return []
else:
return []


class TuyaBLEBinarySensor(TuyaBLEEntity, BinarySensorEntity):
"""Representation of a Tuya BLE binary sensor."""

def __init__(
self,
hass: HomeAssistant,
coordinator: DataUpdateCoordinator,
device: TuyaBLEDevice,
product: TuyaBLEProductInfo,
mapping: TuyaBLEBinarySensorMapping,
) -> None:
super().__init__(hass, coordinator, device, product, mapping.description)
self._mapping = mapping

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if self._mapping.getter is not None:
self._mapping.getter(self)
else:
datapoint = self._device.datapoints[self._mapping.dp_id]
if datapoint:
if datapoint.type == TuyaBLEDataPointType.DT_ENUM:
if self.entity_description.options is not None:
if datapoint.value >= 0 and datapoint.value < len(
self.entity_description.options
):
self._attr_native_value = self.entity_description.options[
datapoint.value
]
else:
self._attr_native_value = datapoint.value
if self._mapping.icons is not None:
if datapoint.value >= 0 and datapoint.value < len(
self._mapping.icons
):
self._attr_icon = self._mapping.icons[datapoint.value]
elif datapoint.type == TuyaBLEDataPointType.DT_VALUE:
self._attr_native_value = (
datapoint.value / self._mapping.coefficient
)
else:
self._attr_native_value = datapoint.value
self.async_write_ha_state()

@property
def available(self) -> bool:
"""Return if entity is available."""
result = super().available
if result and self._mapping.is_available:
result = self._mapping.is_available(self, self._product)
return result


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Tuya BLE sensors."""
data: TuyaBLEData = hass.data[DOMAIN][entry.entry_id]
mappings = get_mapping_by_device(data.device)
entities: list[TuyaBLEBinarySensor] = []
for mapping in mappings:
if mapping.force_add or data.device.datapoints.has_id(
mapping.dp_id, mapping.dp_type
):
entities.append(
TuyaBLEBinarySensor(
hass,
data.coordinator,
data.device,
data.product,
mapping,
)
)
async_add_entities(entities)