diff --git a/README.md b/README.md index 85314f1..e095efe 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Telekom Speedport Integration for Home Assistant based on [speedport-api](https: - Track presence of connected devices - Turn on/off wifi (guest/office/normal) - Reconnect, reboot, wps on +- Sensors (IP-Addresses, Upload/Download, Connection, ...) ## Installation #### Installing via HACS @@ -30,3 +31,10 @@ Telekom Speedport Integration for Home Assistant based on [speedport-api](https: * Speedport Smart 4 So far I can only confirm that it works with my router 🙂 + +## Support +If you find this project helpful and would like to support its development, you can buy me a coffee! ☕ + +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/andre0512) + +Don't forget to star the repository if you found it useful! ⭐ diff --git a/custom_components/speedport/__init__.py b/custom_components/speedport/__init__.py index 1ca27ce..d5cd27f 100644 --- a/custom_components/speedport/__init__.py +++ b/custom_components/speedport/__init__.py @@ -8,7 +8,13 @@ from .const import DOMAIN -PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SWITCH] +PLATFORMS: list[Platform] = [ + Platform.BUTTON, + Platform.DEVICE_TRACKER, + Platform.SWITCH, + Platform.BINARY_SENSOR, + Platform.SENSOR, +] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/custom_components/speedport/binary_sensor.py b/custom_components/speedport/binary_sensor.py new file mode 100644 index 0000000..c885ef4 --- /dev/null +++ b/custom_components/speedport/binary_sensor.py @@ -0,0 +1,57 @@ +import logging + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from speedport import Speedport + +from .const import DOMAIN +from .device import SpeedportEntity + +_LOGGER = logging.getLogger(__name__) + +BINARY_SENSORS: tuple[BinarySensorEntityDescription, ...] = ( + BinarySensorEntityDescription( + key="onlinestatus", + name="Connection", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + ), + BinarySensorEntityDescription( + key="dsl_link_status", + name="DSL-Connection", + device_class=BinarySensorDeviceClass.PLUG, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up entry.""" + speedport: Speedport = hass.data[DOMAIN][entry.entry_id] + + entities = [ + SpeedportBinarySensor(hass, speedport, description) + for description in BINARY_SENSORS + ] + + async_add_entities(entities) + + +class SpeedportBinarySensor(SpeedportEntity, BinarySensorEntity): + entity_description: BinarySensorEntityDescription + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self._speedport.get(self.entity_description.key) == "online" + + def available(self) -> bool: + if self._speedport.get(self.entity_description.key) is None: + return False + return super().available diff --git a/custom_components/speedport/button.py b/custom_components/speedport/button.py index 34a0e8b..dbcb9fd 100644 --- a/custom_components/speedport/button.py +++ b/custom_components/speedport/button.py @@ -3,7 +3,6 @@ from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import HomeAssistantType from speedport import Speedport @@ -29,7 +28,6 @@ async def async_setup_entry( class SpeedportReconnectButton(ButtonEntity, SpeedportEntity): _attr_device_class = ButtonDeviceClass.RESTART - _attr_entity_category = EntityCategory.CONFIG def __init__(self, hass: HomeAssistantType, speedport: Speedport) -> None: """Initialize the button entity.""" @@ -45,7 +43,6 @@ async def async_press(self) -> None: class SpeedportRebootButton(ButtonEntity, SpeedportEntity): _attr_device_class = ButtonDeviceClass.RESTART - _attr_entity_category = EntityCategory.CONFIG def __init__(self, hass: HomeAssistantType, speedport: Speedport) -> None: """Initialize the button entity.""" @@ -61,7 +58,6 @@ async def async_press(self) -> None: class SpeedportWPSButton(ButtonEntity, SpeedportEntity): _attr_device_class = ButtonDeviceClass.IDENTIFY - _attr_entity_category = EntityCategory.CONFIG def __init__(self, hass: HomeAssistantType, speedport: Speedport) -> None: """Initialize the button entity.""" diff --git a/custom_components/speedport/device.py b/custom_components/speedport/device.py index 25652ed..a8bf12d 100644 --- a/custom_components/speedport/device.py +++ b/custom_components/speedport/device.py @@ -1,3 +1,4 @@ +import asyncio import logging from datetime import timedelta @@ -25,21 +26,28 @@ def __init__(self, hass: HomeAssistantType, device: Speedport): name=device.device_name, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) - self._device = device + self._speedport: Speedport = device async def _async_update_data(self) -> None: - return await self._device.update_status() + await asyncio.gather( + *[self._speedport.update_status(), self._speedport.update_ip_data()] + ) class SpeedportEntity(CoordinatorEntity[SpeedportCoordinator]): _attr_has_entity_name = True - def __init__(self, hass: HomeAssistantType, speedport: Speedport) -> None: + def __init__( + self, hass: HomeAssistantType, speedport: Speedport, description=None + ) -> None: coordinator = get_coordinator(hass, speedport) super().__init__(coordinator) self._coordinator = coordinator self._speedport: Speedport = speedport + if description is not None: + self.entity_description = description + self._attr_unique_id = f"speedport_{description.key}" @property def device_info(self) -> DeviceInfo: diff --git a/custom_components/speedport/sensor.py b/custom_components/speedport/sensor.py new file mode 100644 index 0000000..6befaa0 --- /dev/null +++ b/custom_components/speedport/sensor.py @@ -0,0 +1,101 @@ +from datetime import datetime + +import pytz +from homeassistant.components.sensor import ( + SensorEntityDescription, + SensorDeviceClass, + SensorStateClass, + SensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + EntityCategory, + UnitOfDataRate, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType +from speedport import Speedport + +from custom_components.speedport import DOMAIN +from custom_components.speedport.device import SpeedportEntity + +SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="public_ip_v4", + name="IPv4", + icon="mdi:earth", + ), + SensorEntityDescription( + key="public_ip_v6", + name="IPv6", + icon="mdi:earth", + ), + SensorEntityDescription( + key="inet_uptime", + name="Internet Uptime", + device_class=SensorDeviceClass.TIMESTAMP, + ), + SensorEntityDescription( + key="inet_upload", + name="Upload", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND, + device_class=SensorDeviceClass.DATA_RATE, + icon="mdi:upload", + ), + SensorEntityDescription( + key="inet_download", + name="Download", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND, + device_class=SensorDeviceClass.DATA_RATE, + icon="mdi:download", + ), + SensorEntityDescription( + key="dsl_upstream", + name="DSL-Link Upstream", + native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND, + device_class=SensorDeviceClass.DATA_RATE, + icon="mdi:upload", + ), + SensorEntityDescription( + key="dsl_downstream", + name="DSL-Link Downstream", + native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND, + device_class=SensorDeviceClass.DATA_RATE, + icon="mdi:download", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up entry.""" + speedport: Speedport = hass.data[DOMAIN][entry.entry_id] + + entities = [ + SpeedportBinarySensor(hass, speedport, description) for description in SENSORS + ] + + async_add_entities(entities) + + +class SpeedportBinarySensor(SpeedportEntity, SensorEntity): + entity_description: SensorEntityDescription + + @property + def native_value(self) -> StateType: + """Return the value reported by the sensor.""" + if (data := self._speedport.get(self.entity_description.key)) is None: + return None + if self.entity_description.device_class == SensorDeviceClass.TIMESTAMP: + date = datetime.strptime(data, "%Y-%m-%d %H:%M:%S") + return pytz.timezone("Europe/Berlin").localize(date) + return data + + def available(self) -> bool: + if self._speedport.get(self.entity_description.key) is None: + return False + return super().available diff --git a/custom_components/speedport/switch.py b/custom_components/speedport/switch.py index 4d3cbc8..4f39b3e 100644 --- a/custom_components/speedport/switch.py +++ b/custom_components/speedport/switch.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import logging from typing import Any @@ -22,7 +23,7 @@ async def async_setup_entry( """Set up entry.""" speedport: Speedport = hass.data[DOMAIN][entry.entry_id] - await speedport.update_status() + await asyncio.gather(*[speedport.update_status(), speedport.update_ip_data()]) async_add_entities( [ SpeedportWifiSwitch(hass, speedport), diff --git a/requirements.txt b/requirements.txt index e68ad7d..b8a8c27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ speedport-api==0.5.2 homeassistant~=2023.10 +pytz~=2023.3