Skip to content

Commit

Permalink
Add Huawei LTE network mode select (#104614)
Browse files Browse the repository at this point in the history
* Convert network mode from sensor to select for huawei_lte

This also introduces the select platform to huawei_lte integration.

* Move (networkmode, str) mapping to const

Also, rebase on top of the current dev

* Fix variable naming, initialize name

* Fix wrong key for router access

* Typing fixes

* Adapt to current way of registering subscriptions

* Simplify option management, make translatable

* Make use of custom entity description

* Add icon

* Revert sensor formatting changes, move to another PR

* Improve entity class naming

* Add test

* Make sure entity descriptions define a setter function

---------

Co-authored-by: Teemu Rytilahti <tpr@iki.fi>
  • Loading branch information
scop and rytilahti authored Dec 6, 2023
1 parent 81d05ac commit a29695e
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 0 deletions.
1 change: 1 addition & 0 deletions homeassistant/components/huawei_lte/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
Platform.DEVICE_TRACKER,
Platform.SENSOR,
Platform.SWITCH,
Platform.SELECT,
]


Expand Down
132 changes: 132 additions & 0 deletions homeassistant/components/huawei_lte/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Support for Huawei LTE selects."""
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass, field
from functools import partial
import logging

from huawei_lte_api.enums.net import LTEBandEnum, NetworkBandEnum, NetworkModeEnum

from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
SelectEntity,
SelectEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED

from . import HuaweiLteBaseEntityWithDevice
from .const import DOMAIN, KEY_NET_NET_MODE

_LOGGER = logging.getLogger(__name__)


@dataclass
class HuaweiSelectEntityMixin:
"""Mixin for Huawei LTE select entities, to ensure required fields are set."""

setter_fn: Callable[[str], None]


@dataclass
class HuaweiSelectEntityDescription(SelectEntityDescription, HuaweiSelectEntityMixin):
"""Class describing Huawei LTE select entities."""


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up from config entry."""
router = hass.data[DOMAIN].routers[config_entry.entry_id]
selects: list[Entity] = []

desc = HuaweiSelectEntityDescription(
key=KEY_NET_NET_MODE,
entity_category=EntityCategory.CONFIG,
icon="mdi:transmission-tower",
name="Preferred network mode",
translation_key="preferred_network_mode",
options=[
NetworkModeEnum.MODE_AUTO.value,
NetworkModeEnum.MODE_4G_3G_AUTO.value,
NetworkModeEnum.MODE_4G_2G_AUTO.value,
NetworkModeEnum.MODE_4G_ONLY.value,
NetworkModeEnum.MODE_3G_2G_AUTO.value,
NetworkModeEnum.MODE_3G_ONLY.value,
NetworkModeEnum.MODE_2G_ONLY.value,
],
setter_fn=partial(
router.client.net.set_net_mode,
LTEBandEnum.ALL,
NetworkBandEnum.ALL,
),
)
selects.append(
HuaweiLteSelectEntity(
router,
entity_description=desc,
key=desc.key,
item="NetworkMode",
)
)

async_add_entities(selects, True)


@dataclass
class HuaweiLteSelectEntity(HuaweiLteBaseEntityWithDevice, SelectEntity):
"""Huawei LTE select entity."""

entity_description: HuaweiSelectEntityDescription
key: str
item: str

_raw_state: str | None = field(default=None, init=False)

def __post_init__(self) -> None:
"""Initialize remaining attributes."""
name = None
if self.entity_description.name != UNDEFINED:
name = self.entity_description.name
self._attr_name = name or self.item

def select_option(self, option: str) -> None:
"""Change the selected option."""
self.entity_description.setter_fn(option)

@property
def current_option(self) -> str | None:
"""Return current option."""
return self._raw_state

@property
def _device_unique_id(self) -> str:
return f"{self.key}.{self.item}"

async def async_added_to_hass(self) -> None:
"""Subscribe to needed data on add."""
await super().async_added_to_hass()
self.router.subscriptions[self.key].append(f"{SELECT_DOMAIN}/{self.item}")

async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from needed data on remove."""
await super().async_will_remove_from_hass()
self.router.subscriptions[self.key].remove(f"{SELECT_DOMAIN}/{self.item}")

async def async_update(self) -> None:
"""Update state."""
try:
value = self.router.data[self.key][self.item]
except KeyError:
_LOGGER.debug("%s[%s] not in data", self.key, self.item)
self._available = False
return
self._available = True
self._raw_state = str(value)
14 changes: 14 additions & 0 deletions homeassistant/components/huawei_lte/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,20 @@
"name": "SMS messages (SIM)"
}
},
"select": {
"preferred_network_mode": {
"name": "Preferred network mode",
"state": {
"00": "4G/3G/2G auto",
"0302": "4G/3G auto",
"0301": "4G/2G auto",
"03": "4G only",
"0201": "3G/2G auto",
"02": "3G only",
"01": "2G only"
}
}
},
"switch": {
"mobile_data": {
"name": "Mobile data"
Expand Down
43 changes: 43 additions & 0 deletions tests/components/huawei_lte/test_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Tests for the Huawei LTE selects."""
from unittest.mock import MagicMock, patch

from huawei_lte_api.enums.net import LTEBandEnum, NetworkBandEnum, NetworkModeEnum

from homeassistant.components.huawei_lte.const import DOMAIN
from homeassistant.components.select import SERVICE_SELECT_OPTION
from homeassistant.components.select.const import DOMAIN as SELECT_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, CONF_URL
from homeassistant.core import HomeAssistant

from . import magic_client

from tests.common import MockConfigEntry

SELECT_NETWORK_MODE = "select.lte_preferred_network_mode"


@patch("homeassistant.components.huawei_lte.Connection", MagicMock())
@patch("homeassistant.components.huawei_lte.Client")
async def test_set_net_mode(client, hass: HomeAssistant) -> None:
"""Test setting network mode."""
client.return_value = magic_client({})
huawei_lte = MockConfigEntry(
domain=DOMAIN, data={CONF_URL: "http://huawei-lte.example.com"}
)
huawei_lte.add_to_hass(hass)
await hass.config_entries.async_setup(huawei_lte.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: SELECT_NETWORK_MODE,
ATTR_OPTION: NetworkModeEnum.MODE_4G_3G_AUTO.value,
},
blocking=True,
)
await hass.async_block_till_done()
client.return_value.net.set_net_mode.assert_called_once()
client.return_value.net.set_net_mode.assert_called_with(
LTEBandEnum.ALL, NetworkBandEnum.ALL, NetworkModeEnum.MODE_4G_3G_AUTO.value
)

0 comments on commit a29695e

Please sign in to comment.