Skip to content

Commit 5856781

Browse files
committed
Added website filtering switches
1 parent 7357018 commit 5856781

21 files changed

+566
-67
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Home Assistant custom component for control Huawei mesh routers over LAN.
2121
- enable or disable Wi-Fi access control
2222
- add/remove devices from blacklist/whitelist
2323
- Wi-Fi access switch per client device
24+
- enabling and disabling access to specific sites
2425
- hardware and firmware version of the primary router
2526
- internet connection details
2627
- uptime of each router
@@ -77,6 +78,7 @@ Advanced settings include:
7778
| Enabling or disabling [Device tags](docs/device-tags.md#device-tags) | Disabled |
7879
| Enabling or disabling [Devices tracking](docs/device-tracking.md#devices-tracking) | Disabled |
7980
| Enabling or disabling [Router-specific zones](docs/device-tracking.md#router-specific-zones) | Disabled |
81+
| Enabling or disabling [Website filtering switches](docs/controls.md#website-filtering) | Disabled |
8082

8183

8284
![Options 1/2](docs/images/options_1.png)
@@ -109,6 +111,7 @@ You can attach one or more tags to each client device in order to be able to use
109111
* Wi-Fi TWT switch ([read more](docs/controls.md#wi-fi-6-twt-switch))
110112
* Wi-Fi Access Control ([read more](docs/controls.md#wi-fi-access-control))
111113
* Device Wi-Fi Access ([read more](docs/controls.md#device-wi-fi-access))
114+
* Website filtering ([read more](docs/controls.md#website-filtering))
112115

113116
### Selects
114117
* Wi-Fi access control mode ([read more](docs/controls.md#wi-fi-access-control-mode))

custom_components/huawei_mesh_router/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
from .const import (
1414
DEFAULT_DEVICE_TRACKER_ZONES,
1515
DEFAULT_SCAN_INTERVAL,
16+
DEFAULT_URL_FILTER_SWITCHES,
1617
DOMAIN,
1718
OPT_DEVICE_TRACKER,
1819
OPT_DEVICE_TRACKER_ZONES,
1920
OPT_DEVICES_TAGS,
2021
OPT_ROUTER_CLIENTS_SENSORS,
22+
OPT_URL_FILTER_SWITCHES,
2123
OPT_WIFI_ACCESS_SWITCHES,
2224
PLATFORMS,
2325
STORAGE_VERSION,
@@ -158,6 +160,11 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
158160
updated_options[OPT_DEVICE_TRACKER_ZONES] = DEFAULT_DEVICE_TRACKER_ZONES
159161
config_entry.version = 3
160162

163+
if config_entry.version == 3:
164+
_LOGGER.debug("Migrating to version 4")
165+
updated_options[OPT_URL_FILTER_SWITCHES] = DEFAULT_URL_FILTER_SWITCHES
166+
config_entry.version = 4
167+
161168
hass.config_entries.async_update_entry(
162169
config_entry, data=updated_data, options=updated_options
163170
)

custom_components/huawei_mesh_router/button.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def async_setup_entry(
5151
def _watch_for_additional_routers(
5252
coordinator, config_entry, async_add_entities
5353
) -> None:
54-
watcher: ActiveRoutersWatcher = ActiveRoutersWatcher()
54+
watcher: ActiveRoutersWatcher = ActiveRoutersWatcher(coordinator)
5555
known_buttons: dict[MAC_ADDR, HuaweiButton] = {}
5656

5757
@callback
@@ -65,7 +65,7 @@ def on_router_added(mac: MAC_ADDR, router: ConnectedDevice) -> None:
6565
@callback
6666
def coordinator_updated() -> None:
6767
"""Update the status of the device."""
68-
watcher.look_for_changes(coordinator, on_router_added)
68+
watcher.look_for_changes(on_router_added)
6969

7070
config_entry.async_on_unload(coordinator.async_add_listener(coordinator_updated))
7171
coordinator_updated()

custom_components/huawei_mesh_router/classes.py

+55-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from homeassistant.backports.enum import StrEnum
77

8-
from .client.classes import MAC_ADDR
8+
from .client.classes import MAC_ADDR, HuaweiFilterItem
99

1010
DEVICE_TAG = str
1111

@@ -36,6 +36,60 @@ class HuaweiWlanFilterMode(StrEnum):
3636
WHITELIST = "Whitelist"
3737

3838

39+
# ---------------------------
40+
# UrlFilter
41+
# ---------------------------
42+
class UrlFilter:
43+
def __init__(
44+
self,
45+
filter_id: str,
46+
url: str,
47+
enabled: bool,
48+
dev_manual: bool,
49+
devices: Iterable[HuaweiFilterItem],
50+
) -> None:
51+
self._filter_id = filter_id
52+
self._url = url
53+
self._dev_manual = dev_manual
54+
self._devices = list(devices)
55+
self._enabled = enabled
56+
57+
def update_info(
58+
self,
59+
url: str,
60+
enabled: bool,
61+
dev_manual: bool,
62+
devices: Iterable[HuaweiFilterItem],
63+
) -> None:
64+
self._url = url
65+
self._dev_manual = dev_manual
66+
self._devices = list(devices)
67+
self._enabled = enabled
68+
69+
def set_enabled(self, enabled: bool) -> None:
70+
self._enabled = enabled
71+
72+
@property
73+
def filter_id(self) -> str:
74+
return self._filter_id
75+
76+
@property
77+
def url(self) -> str:
78+
return self._url
79+
80+
@property
81+
def enabled(self) -> bool:
82+
return self._enabled
83+
84+
@property
85+
def dev_manual(self) -> bool:
86+
return self._dev_manual
87+
88+
@property
89+
def devices(self) -> Iterable[HuaweiFilterItem]:
90+
return self._devices
91+
92+
3993
# ---------------------------
4094
# ConnectedDevice
4195
# ---------------------------

custom_components/huawei_mesh_router/client/classes.py

+12
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ class HuaweiFilterItem:
3939
name: str | None = None
4040

4141

42+
# ---------------------------
43+
# HuaweiUrlFilterInfo
44+
# ---------------------------
45+
@dataclass()
46+
class HuaweiUrlFilterInfo:
47+
filter_id: str
48+
url: str
49+
enabled: bool
50+
dev_manual: bool
51+
devices: list[HuaweiFilterItem]
52+
53+
4254
# ---------------------------
4355
# HuaweiFilterItem
4456
# ---------------------------

custom_components/huawei_mesh_router/client/huaweiapi.py

+54-3
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,24 @@
1414
HuaweiConnectionInfo,
1515
HuaweiDeviceNode,
1616
HuaweiFilterInfo,
17+
HuaweiFilterItem,
1718
HuaweiRouterInfo,
19+
HuaweiUrlFilterInfo,
1820
)
1921
from .coreapi import APICALL_ERRCAT_UNAUTHORIZED, ApiCallError, HuaweiCoreApi
2022

2123
SWITCH_NFC: Final = "nfc_switch"
2224
SWITCH_WIFI_80211R: Final = "wifi_80211r_switch"
2325
SWITCH_WIFI_TWT: Final = "wifi_twt_switch"
2426
SWITCH_WLAN_FILTER: Final = "wlan_filter_switch"
27+
SWITCH_URL_FILTER: Final = "url_filter_switch"
2528

2629
ACTION_REBOOT: Final = "reboot_action"
2730

2831
CONNECTED_VIA_ID_PRIMARY: Final = "primary"
2932

3033
FEATURE_NFC: Final = "feature_nfc"
34+
FEATURE_URL_FILTER: Final = "feature_url_filter"
3135
FEATURE_WIFI_80211R: Final = "feature_wifi_80211r"
3236
FEATURE_WIFI_TWT: Final = "feature_wifi_twt"
3337
FEATURE_WLAN_FILTER: Final = "feature_wlan_filter"
@@ -43,6 +47,7 @@
4347
_URL_REPEATER_INFO: Final = "api/ntwk/repeaterinfo"
4448
_URL_WANDETECT: Final = "api/ntwk/wandetect"
4549
_URL_WLAN_FILTER: Final = "api/ntwk/wlanfilterenhance"
50+
_URL_URL_FILTER: Final = "api/ntwk/urlfilter"
4651

4752
_STATUS_CONNECTED: Final = "Connected"
4853

@@ -153,6 +158,12 @@ async def _is_device_topology_available(self) -> bool:
153158
data = await self._core_api.get(_URL_DEVICE_TOPOLOGY)
154159
return data is not None
155160

161+
@log_feature(FEATURE_URL_FILTER)
162+
@unauthorized_as_false
163+
async def _is_url_filter_available(self) -> bool:
164+
data = await self._core_api.get(_URL_URL_FILTER)
165+
return data is not None
166+
156167
async def update(self) -> None:
157168
"""Update the available features list."""
158169
if await self._is_nfc_available():
@@ -170,14 +181,14 @@ async def update(self) -> None:
170181
if await self._is_device_topology_available():
171182
self._available_features.add(FEATURE_DEVICE_TOPOLOGY)
172183

184+
if await self._is_url_filter_available():
185+
self._available_features.add(FEATURE_URL_FILTER)
186+
173187
def is_available(self, feature: str) -> bool:
174188
"""Return true if feature is available."""
175189
return feature in self._available_features
176190

177191

178-
# ---------------------------
179-
# HuaweiApi
180-
# ---------------------------
181192
class HuaweiApi:
182193
def __init__(
183194
self,
@@ -530,6 +541,27 @@ async def _get_filter_states(self):
530541
state_5g = state
531542
return state_2g, state_5g
532543

544+
async def apply_url_filter_info(self, url_filter_info: HuaweiUrlFilterInfo) -> None:
545+
actual = await self.get_url_filter_info()
546+
existing_item = next((item for item in actual if item.filter_id == url_filter_info.filter_id))
547+
548+
action: str = "update" if existing_item else "create"
549+
550+
data: dict[str, Any] = {
551+
"Devices": [
552+
{"MACAddress": device.mac_address} for device in url_filter_info.devices
553+
],
554+
"DeviceNames": [
555+
{"HostName": device.name} for device in url_filter_info.devices
556+
],
557+
"DevManual": url_filter_info.dev_manual,
558+
"URL": url_filter_info.url,
559+
"Status": 2 if url_filter_info.enabled else 0,
560+
"ID": url_filter_info.filter_id,
561+
}
562+
563+
await self._core_api.post(_URL_URL_FILTER, data, extra_data={"action": action})
564+
533565
async def _process_access_lists(
534566
self,
535567
state: dict[str, Any],
@@ -660,3 +692,22 @@ async def get_is_repeater(self) -> bool:
660692
repeater_enable = data.get("RepeaterEnable", False)
661693

662694
return isinstance(repeater_enable, bool) and repeater_enable
695+
696+
@staticmethod
697+
def _to_url_filter_info(data: dict[str, Any]) -> HuaweiUrlFilterInfo:
698+
result = HuaweiUrlFilterInfo(
699+
filter_id=data["ID"],
700+
url=data["URL"],
701+
enabled=data.get("Status", -1) == 2,
702+
dev_manual=data.get("DevManual") is True,
703+
devices=[
704+
HuaweiFilterItem(item[0].get("MACAddress"), item[1].get("HostName"))
705+
for item in zip(data["Devices"], data["DeviceNames"])
706+
],
707+
)
708+
709+
return result
710+
711+
async def get_url_filter_info(self) -> Iterable[HuaweiUrlFilterInfo]:
712+
data = await self._core_api.get(_URL_URL_FILTER)
713+
return [self._to_url_filter_info(item) for item in data]

custom_components/huawei_mesh_router/config_flow.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
DEFAULT_ROUTER_CLIENTS_SENSORS,
3131
DEFAULT_SCAN_INTERVAL,
3232
DEFAULT_SSL,
33+
DEFAULT_URL_FILTER_SWITCHES,
3334
DEFAULT_USER,
3435
DEFAULT_VERIFY_SSL,
3536
DEFAULT_WIFI_ACCESS_SWITCHES,
@@ -38,6 +39,7 @@
3839
OPT_DEVICE_TRACKER_ZONES,
3940
OPT_DEVICES_TAGS,
4041
OPT_ROUTER_CLIENTS_SENSORS,
42+
OPT_URL_FILTER_SWITCHES,
4143
OPT_WIFI_ACCESS_SWITCHES,
4244
)
4345

@@ -61,7 +63,7 @@ def configured_instances(hass):
6163
class HuaweiControllerConfigFlow(ConfigFlow, domain=DOMAIN):
6264
"""HuaweiControllerConfigFlow class"""
6365

64-
VERSION = 3
66+
VERSION = 4
6567

6668
def __init__(self):
6769
"""Initialize HuaweiControllerConfigFlow."""
@@ -227,6 +229,12 @@ async def async_step_features_select(self, user_input=None) -> FlowResult:
227229
OPT_DEVICE_TRACKER_ZONES, DEFAULT_DEVICE_TRACKER_ZONES
228230
),
229231
): bool,
232+
vol.Required(
233+
OPT_URL_FILTER_SWITCHES,
234+
default=self.options.get(
235+
OPT_URL_FILTER_SWITCHES, DEFAULT_URL_FILTER_SWITCHES
236+
),
237+
): bool,
230238
},
231239
),
232240
)

custom_components/huawei_mesh_router/const.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
DATA_KEY_SERVICES: Final = "services_count"
1414

1515
OPT_WIFI_ACCESS_SWITCHES = "wifi_access_switches"
16+
OPT_URL_FILTER_SWITCHES = "url_filter_switches"
1617
OPT_ROUTER_CLIENTS_SENSORS = "router_clients_sensors"
1718
OPT_DEVICES_TAGS = "devices_tags"
1819
OPT_DEVICE_TRACKER = "device_tracker"
@@ -31,6 +32,7 @@
3132
DEFAULT_DEVICES_TAGS: Final = False
3233
DEFAULT_DEVICE_TRACKER: Final = False
3334
DEFAULT_DEVICE_TRACKER_ZONES: Final = False
35+
DEFAULT_URL_FILTER_SWITCHES: Final = False
3436

3537
ATTR_MANUFACTURER: Final = "Huawei"
3638
PLATFORMS: Final = [

custom_components/huawei_mesh_router/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
"@vmakeev"
1212
],
1313
"iot_class": "local_polling",
14-
"version": "0.8.5",
14+
"version": "0.8.6",
1515
"config_flow": true
1616
}

custom_components/huawei_mesh_router/options.py

+9
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
DEFAULT_DEVICES_TAGS,
1010
DEFAULT_ROUTER_CLIENTS_SENSORS,
1111
DEFAULT_SCAN_INTERVAL,
12+
DEFAULT_URL_FILTER_SWITCHES,
1213
DEFAULT_WIFI_ACCESS_SWITCHES,
1314
OPT_DEVICE_TRACKER,
1415
OPT_DEVICE_TRACKER_ZONES,
1516
OPT_DEVICES_TAGS,
1617
OPT_ROUTER_CLIENTS_SENSORS,
18+
OPT_URL_FILTER_SWITCHES,
1719
OPT_WIFI_ACCESS_SWITCHES,
1820
)
1921

@@ -66,6 +68,13 @@ def device_tracker_zones(self) -> bool:
6668
self._config_entry, OPT_DEVICE_TRACKER_ZONES, DEFAULT_DEVICE_TRACKER_ZONES
6769
)
6870

71+
@property
72+
def url_filter_switches(self) -> bool:
73+
"""Return option 'url filter switches' value"""
74+
return get_option(
75+
self._config_entry, OPT_URL_FILTER_SWITCHES, DEFAULT_URL_FILTER_SWITCHES
76+
)
77+
6978
@property
7079
def router_clients_sensors(self) -> bool:
7180
"""Return option 'router clients sensors' value"""

custom_components/huawei_mesh_router/select.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def watch_for_additional_routers(
6969
config_entry: ConfigEntry,
7070
async_add_entities: AddEntitiesCallback,
7171
) -> None:
72-
router_watcher: ActiveRoutersWatcher = ActiveRoutersWatcher()
72+
router_watcher: ActiveRoutersWatcher = ActiveRoutersWatcher(coordinator)
7373
known_zone_selects: dict[MAC_ADDR, HuaweiRouterZoneSelect] = {}
7474

7575
@callback
@@ -83,7 +83,7 @@ def on_router_added(device_mac: MAC_ADDR, router: ConnectedDevice) -> None:
8383
@callback
8484
def coordinator_updated() -> None:
8585
"""Update the status of the device."""
86-
router_watcher.look_for_changes(coordinator, on_router_added)
86+
router_watcher.look_for_changes(on_router_added)
8787

8888
config_entry.async_on_unload(coordinator.async_add_listener(coordinator_updated))
8989
coordinator_updated()

custom_components/huawei_mesh_router/sensor.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def watch_for_additional_routers(
170170
integration_options: HuaweiIntegrationOptions,
171171
async_add_entities: AddEntitiesCallback,
172172
):
173-
watcher: ActiveRoutersWatcher = ActiveRoutersWatcher()
173+
watcher: ActiveRoutersWatcher = ActiveRoutersWatcher(coordinator)
174174
known_client_sensors: dict[MAC_ADDR, HuaweiConnectedDevicesSensor] = {}
175175
known_uptime_sensors: dict[MAC_ADDR, HuaweiUptimeSensor] = {}
176176

@@ -227,7 +227,7 @@ def on_router_added(mac: MAC_ADDR, router: ConnectedDevice) -> None:
227227
@callback
228228
def coordinator_updated() -> None:
229229
"""Update the status of the device."""
230-
watcher.look_for_changes(coordinator, on_router_added)
230+
watcher.look_for_changes(on_router_added)
231231

232232
config_entry.async_on_unload(coordinator.async_add_listener(coordinator_updated))
233233
coordinator_updated()

0 commit comments

Comments
 (0)