Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ If you forget to enable it back - it would be automatically enable after 20 minu
- Memory used
- Connection Type

For LTE Routers
- LTE Enabled
- LTE Connection Status
- LTE Network Type
- LTE SIM Status
- LTE Total Statistics
- LTE Current RX Speed
- LTE Current TX Speed
- Unread SMS
- LTE Signal Level
- LTE RSRP
- LTE RSRQ
- LTE SNR
- LTE ISP Name

### Device Tracker
- Track connected to router devices by MAC address with connection information

Expand Down Expand Up @@ -191,6 +206,7 @@ To do that:
- Archer C7 (v4.0, v5.0)
- Archer C24 (1.0, 2.0)
- Archer C60 v2.0
- Archer C64 1.0
- Archer C80 (1.0, 2.20)
- Archer C5400X V1
- Archer GX90 v1.0
Expand Down Expand Up @@ -223,7 +239,9 @@ To do that:
- TD-W9960 (v1, V1.20)
- TL-MR100 v2.0
- TL-MR105
- TL-MR100-Outdoor v1.0
- TL-MR110-Outdoor v1.0
- TL-MR150 v2
- TL-MR6400 (v5, v5.3)
- TL-MR6500v
- TL-WA1201 3.0
Expand All @@ -236,7 +254,9 @@ To do that:
- MR47BE v1.0
- MR50G 1.0
- H60XR 1.0
- H47BE 2.0
- Halo H80X 1.0
- Halo H3000x 1.0

Please let me know if you have tested integration with any other model. Open an issue with info about router's model, hardware and firmware versions.

Expand Down
10 changes: 6 additions & 4 deletions custom_components/tplink_router/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
def callback():
firm = client.get_firmware()
stat = client.get_status()
# Check if router is lte_status compatible
lte_status = client.get_lte_status() if hasattr(client, "get_lte_status") else None

return firm, stat
return firm, stat, lte_status

firmware, status = await hass.async_add_executor_job(TPLinkRouterCoordinator.request, client, callback)
firmware, status, lte_status = await hass.async_add_executor_job(TPLinkRouterCoordinator.request, client, callback)

# Create device coordinator and fetch data
coordinator = TPLinkRouterCoordinator(hass, client, entry.data[CONF_SCAN_INTERVAL], firmware, status, _LOGGER,
entry.entry_id)
coordinator = TPLinkRouterCoordinator(hass, client, entry.data[CONF_SCAN_INTERVAL], firmware, status,
lte_status, _LOGGER, entry.entry_id)

await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
Expand Down
12 changes: 11 additions & 1 deletion custom_components/tplink_router/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from logging import Logger
from collections.abc import Callable
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from tplinkrouterc6u import TplinkRouterProvider, AbstractRouter, Firmware, Status, Connection
from tplinkrouterc6u import TplinkRouterProvider, AbstractRouter, Firmware, Status, Connection, LTEStatus
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from .const import (
Expand All @@ -20,12 +20,15 @@ def __init__(
update_interval: int,
firmware: Firmware,
status: Status,
lte_status: LTEStatus | None,
logger: Logger,
unique_id: str
) -> None:
self.router = router
self.unique_id = unique_id
self.status = status
self.tracked = {}
self.lte_status = lte_status
self.device_info = DeviceInfo(
configuration_url=router.host,
connections={(CONNECTION_NETWORK_MAC, self.status.lan_macaddr)},
Expand Down Expand Up @@ -76,3 +79,10 @@ async def _async_update_data(self):
self.scan_stopped_at = None
self.status = await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router,
self.router.get_status)
# Only fetch if router is lte_status compatible
if self.lte_status is not None:
self.lte_status = await self.hass.async_add_executor_job(
TPLinkRouterCoordinator.request,
self.router,
self.router.get_lte_status,
)
4 changes: 2 additions & 2 deletions custom_components/tplink_router/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://github.com/AlexandrErohin/home-assistant-tplink-router",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/AlexandrErohin/home-assistant-tplink-router/issues",
"requirements": ["tplinkrouterc6u==5.9.3"],
"version": "2.9.0"
"requirements": ["tplinkrouterc6u==5.9.4"],
"version": "2.10.0"
}
126 changes: 114 additions & 12 deletions custom_components/tplink_router/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import PERCENTAGE
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, UnitOfDataRate
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import TPLinkRouterCoordinator
from tplinkrouterc6u import Status, IPv4Status
from tplinkrouterc6u import Status, LTEStatus


@dataclass
Expand All @@ -22,8 +22,8 @@ class TPLinkRouterSensorRequiredKeysMixin:


@dataclass
class TPLinkRouterIpv4SensorRequiredKeysMixin:
value: Callable[[IPv4Status], Any]
class TPLinkRouterLTESensorRequiredKeysMixin:
value: Callable[[LTEStatus], Any]


@dataclass
Expand All @@ -36,12 +36,12 @@ class TPLinkRouterSensorEntityDescription(


@dataclass
class TPLinkRouterIpv4SensorEntityDescription(
SensorEntityDescription, TPLinkRouterIpv4SensorRequiredKeysMixin
class TPLinkRouterLTESensorEntityDescription(
SensorEntityDescription, TPLinkRouterLTESensorRequiredKeysMixin
):
"""A class that describes Ipv4Sensor entities."""
"""A class that describes LTEStatus entities."""

sensor_type: str = "ipv4_status"
sensor_type: str = "lte_status"


SENSOR_TYPES: tuple[TPLinkRouterSensorEntityDescription, ...] = (
Expand Down Expand Up @@ -107,6 +107,102 @@ class TPLinkRouterIpv4SensorEntityDescription(
)


LTE_SENSOR_TYPES: tuple[TPLinkRouterLTESensorEntityDescription, ...] = (
TPLinkRouterLTESensorEntityDescription(
key="lte_enabled",
name="LTE Enabled",
icon="mdi:sim-outline",
value=lambda status: status.enable,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_connect_status",
name="LTE Connection Status",
icon="mdi:sim-outline",
value=lambda status: status.connect_status,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_network_type",
name="LTE Network Type",
icon="mdi:sim-outline",
value=lambda status: status.network_type,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_sim_status",
name="LTE SIM Status",
icon="mdi:sim-outline",
value=lambda status: status.sim_status,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_total_statistics",
name="LTE Total Statistics",
icon="mdi:sim-outline",
state_class=SensorStateClass.TOTAL,
value=lambda status: status.total_statistics,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_cur_rx_speed",
name="LTE Current RX Speed",
icon="mdi:sim-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
value=lambda status: status.cur_rx_speed,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_cur_tx_speed",
name="LTE Current TX Speed",
icon="mdi:sim-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
value=lambda status: status.cur_tx_speed,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_sms_unread_count",
name="Unread SMS",
icon="mdi:sim-outline",
state_class=SensorStateClass.TOTAL,
value=lambda status: status.sms_unread_count,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_sig_level",
name="LTE Signal Level",
icon="mdi:sim-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
value=lambda status: status.sig_level,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_rsrp",
name="LTE RSRP",
icon="mdi:sim-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
value=lambda status: status.rsrp,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_rsrq",
name="LTE RSRQ",
icon="mdi:sim-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
value=lambda status: status.rsrq,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_snr",
name="LTE SNR",
icon="mdi:sim-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
value=lambda status: status.snr,
),
TPLinkRouterLTESensorEntityDescription(
key="lte_isp_name",
name="LTE ISP Name",
icon="mdi:sim-outline",
value=lambda status: status.isp_name,
),
)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
Expand All @@ -117,19 +213,23 @@ async def async_setup_entry(
for description in SENSOR_TYPES:
sensors.append(TPLinkRouterSensor(coordinator, description))

if hasattr(coordinator.router, "get_lte_status"):
for description in LTE_SENSOR_TYPES:
sensors.append(TPLinkRouterSensor(coordinator, description))

async_add_entities(sensors, False)


class TPLinkRouterSensor(
CoordinatorEntity[TPLinkRouterCoordinator], SensorEntity
):
_attr_has_entity_name = True
entity_description: TPLinkRouterSensorEntityDescription
entity_description: TPLinkRouterSensorEntityDescription | TPLinkRouterLTESensorEntityDescription

def __init__(
self,
coordinator: TPLinkRouterCoordinator,
description: TPLinkRouterSensorEntityDescription,
description: TPLinkRouterSensorEntityDescription | TPLinkRouterLTESensorEntityDescription,
) -> None:
super().__init__(coordinator)

Expand All @@ -140,10 +240,12 @@ def __init__(
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_native_value = self.entity_description.value(self.coordinator.status)
coordinator_data = getattr(self.coordinator, self.entity_description.sensor_type)
self._attr_native_value = self.entity_description.value(coordinator_data)
self.async_write_ha_state()

@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.entity_description.value(self.coordinator.status) is not None
coordinator_data = getattr(self.coordinator, self.entity_description.sensor_type)
return self.entity_description.value(coordinator_data) is not None