Skip to content

Commit f9e79d9

Browse files
authored
Add files via upload
1 parent 2e4193e commit f9e79d9

5 files changed

Lines changed: 32 additions & 22 deletions

File tree

custom_components/firewalla/__init__.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ async def async_remove_config_entry_device(
129129
"""Handle the 'Delete' button on a device page in the HA UI.
130130
131131
Called by HA when the user clicks Delete on a device card.
132-
Box devices are not deletable via this path — return False to block.
132+
Box devices and the MSP service device are not deletable via this path —
133+
return False to block.
133134
For network devices, call the Firewalla API to remove the device record,
134135
then return True to allow HA to remove it from the device registry.
135136
If the API call fails we still return True so the user can clean up
@@ -140,8 +141,10 @@ async def async_remove_config_entry_device(
140141
for domain, identifier in device_entry.identifiers:
141142
if domain != DOMAIN:
142143
continue
143-
if identifier.startswith("box_"):
144-
# Box devices should not be deleted from here
144+
# v2.4.9.1: Block deletion of box devices AND the MSP service device.
145+
# Without the msp_global_ check, clicking Delete on the 'Firewalla MSP'
146+
# device card orphans all MSP-level entities until next reload.
147+
if identifier.startswith("box_") or identifier.startswith("msp_global_"):
145148
return False
146149
fw_device_id = identifier
147150

@@ -348,8 +351,10 @@ async def _handle_rename_device(call: ServiceCall) -> None:
348351
for domain, identifier in device_entry.identifiers:
349352
if domain != DOMAIN:
350353
continue
351-
if identifier.startswith("box_"):
352-
continue # Skip box identifiers
354+
# v2.4.9.1: Skip box and MSP service device identifiers —
355+
# these are not renamable network devices.
356+
if identifier.startswith("box_") or identifier.startswith("msp_global_"):
357+
continue
353358
fw_device_id = identifier
354359

355360
if not fw_device_id:

custom_components/firewalla/api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ async def _api_request(
138138
ct = response.headers.get("Content-Type", "")
139139
if "text/html" in ct:
140140
body = await response.text()
141-
if "<html" in body:
141+
# v2.4.9.1: Case-insensitive check — some WAF/proxy pages
142+
# use <HTML>, <Html>, or other casing.
143+
if "<html" in body.lower():
142144
_LOGGER.error(
143145
"HTML instead of JSON from %s (HTTP %s)",
144146
url,

custom_components/firewalla/binary_sensor.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,19 @@ def _safe_configuration_url(raw_ip: str | None) -> str | None:
3434
Validates the value is a real IP address before embedding it in a URL
3535
shown on the HA device card. Rejects hostnames, empty strings, and
3636
malformed values that could construct an unexpected URL.
37+
IPv6 addresses are wrapped in brackets per RFC 2732.
3738
"""
3839
if not raw_ip:
3940
return None
4041
try:
41-
ipaddress.ip_address(raw_ip)
42+
addr = ipaddress.ip_address(raw_ip)
4243
except ValueError:
4344
_LOGGER.debug("Ignoring invalid publicIP value: %s", raw_ip)
4445
return None
45-
return f"https://{raw_ip}"
46+
# v2.4.9.1: IPv6 addresses must be bracketed in URLs (RFC 2732).
47+
if isinstance(addr, ipaddress.IPv6Address):
48+
return f"https://[{addr}]"
49+
return f"https://{addr}"
4650

4751

4852
async def async_setup_entry(

custom_components/firewalla/const.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ class FirewallaAuthError(Exception):
1010
"""
1111

1212
DOMAIN: Final = "firewalla"
13-
BRAND: Final = "Firewalla"
1413
PLATFORMS: Final = ["sensor", "binary_sensor", "switch", "device_tracker"]
1514

1615
# Configuration keys
1716
CONF_API_TOKEN: Final = "api_token"
1817
CONF_SUBDOMAIN: Final = "subdomain"
19-
CONF_SCAN_INTERVAL: Final = "scan_interval"
18+
# Note: CONF_SCAN_INTERVAL is imported from homeassistant.const by consumers.
2019
CONF_ENABLE_ALARMS: Final = "enable_alarms"
2120
CONF_ENABLE_RULES: Final = "enable_rules"
2221
CONF_ENABLE_FLOWS: Final = "enable_flows"
@@ -43,17 +42,6 @@ class FirewallaAuthError(Exception):
4342
SERVICE_SEARCH_ALARMS: Final = "search_alarms"
4443
SERVICE_SEARCH_FLOWS: Final = "search_flows"
4544

46-
# Entity attributes
47-
ATTR_DEVICE_ID: Final = "device_id"
48-
ATTR_DEVICE_NAME: Final = "device_name"
49-
ATTR_NETWORK_ID: Final = "network_id"
50-
ATTR_LAST_SEEN: Final = "last_seen"
51-
ATTR_IP_ADDRESS: Final = "ip_address"
52-
ATTR_MAC_ADDRESS: Final = "mac_address"
53-
ATTR_ONLINE: Final = "online"
54-
ATTR_BLOCKED: Final = "blocked"
55-
ATTR_UPLOAD: Final = "upload"
56-
ATTR_DOWNLOAD: Final = "download"
57-
ATTR_BLOCKED_COUNT: Final = "blocked_count"
45+
# Entity attributes (only constants actively imported by platform modules)
5846
ATTR_ALARM_ID: Final = "alarm_id"
5947
ATTR_RULE_ID: Final = "rule_id"

custom_components/firewalla/sensor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ def __init__(
143143
)
144144

145145
def _get_device(self) -> dict[str, Any] | None:
146+
# v2.4.9.1: Guard against coordinator.data being None — matches the
147+
# pattern used in binary_sensor.py, switch.py, and device_tracker.py.
148+
if not self.coordinator.data:
149+
return None
146150
return next(
147151
(
148152
d
@@ -342,6 +346,9 @@ def __init__(
342346

343347
@property
344348
def native_value(self) -> float | None:
349+
# v2.4.9.1: Guard against coordinator.data being None.
350+
if not self.coordinator.data:
351+
return None
345352
flow = next(
346353
(
347354
f
@@ -484,6 +491,10 @@ def __init__(
484491

485492
def _get_tl(self) -> dict[str, Any] | None:
486493
"""Find this target list in the latest coordinator data."""
494+
# v2.4.9.1: Guard against coordinator.data being None — matches the
495+
# pattern used in binary_sensor.py, switch.py, and device_tracker.py.
496+
if not self.coordinator.data:
497+
return None
487498
return next(
488499
(
489500
tl

0 commit comments

Comments
 (0)